通过引用传递 std::optional<T> 是不是实际上保存复制?

Posted

技术标签:

【中文标题】通过引用传递 std::optional<T> 是不是实际上保存复制?【英文标题】:Does passing an std::optional<T> by reference actually save copying?通过引用传递 std::optional<T> 是否实际上保存复制? 【发布时间】:2021-11-15 12:46:58 【问题描述】:

我知道标准不支持std::optional&lt;T&amp;&gt;。这个问题是关于通过std::optional&lt;T&gt;&amp;是否有任何性能优势

此处转载示例代码 (https://godbolt.org/z/h56Pj6d6z)

#include <ctime>
#include <iomanip>
#include <iostream>
#include <optional>

void DoStuff(std::optional<std::string> str) 
    if (str) std::cout << "cop: " << *str << std::endl;


void DoStuffRef(const std::optional<std::string>& str) 
    if (str) std::cout << "ref: " << *str << std::endl;


int main() 
    std::optional<std::string> str = ;
    DoStuff(str);
    DoStuffRef(str);
    str = "0123456789012345678901234567890123456789";
    DoStuff(str);
    DoStuffRef(str);

(我的实际用例对于复杂的用户定义类型是可选的,但我希望长字符串在编译器方面也能做到这一点)

在这种情况下,与DoStuff 相比,DoStuffRef 是否实际上节省了任何复制工作?

我试图查看 Godbolt 输出,但我不知道足够的汇编来确定。我确实看到在DoStuff 的情况下,似乎创建了一个临时std::optional&lt;T&gt;,而DoStuffRef 中不存在所以我怀疑是的,通过引用传递optional 保存一些复制

感谢您的帮助!

【问题讨论】:

是的,当可选项实际包含字符串时,您会保存一份副本。 【参考方案1】:

如果您传递实际的std::optional&lt;std::string&gt;,那么是的,不会有副本。但如果你只传递std::string,那么必须先构造临时可选,从而生成字符串的副本。

【讨论】:

所以在这种情况下,当我将str 分配给一个字符串值并传递它时,由于str 仍然是std::optional&lt;std::string&gt; 类型,我应该使用DoStuffRef 保存一个副本对? 我认为在 C++17 中它不会创建副本,因为 en.cppreference.com/w/cpp/utility/optional/optional 中的“(8)” @resnet 完全正确。 @alfC 当然,如果std::string 是右值,如果是副本,它将是一个移动,但仍会构建临时的。【参考方案2】:

DoStuffRef 在参数已经为 std::optional&lt;std::string&gt; 时保存一个额外的副本(如您的示例中所示)。

但是,如果你直接传递一个std::string,那么如果这两种情况都应该构造一个std::optional,涉及到字符串的复制/移动构造函数。

【讨论】:

以上是关于通过引用传递 std::optional<T> 是不是实际上保存复制?的主要内容,如果未能解决你的问题,请参考以下文章

std::optional<T> 的开销?

如何使用 std::optional<T>::emplace 的第二个重载

如何优雅处理多参数返回/无参数返回——std::optional

是否有一种紧凑的方法可以使 std::optional<T>::value_or 对 T 的成员变量起作用

带有 std::any 和 std::optional 的 any_cast

为啥允许将 std::optional 与值进行比较? [复制]