在下面的MVE中,Get
函数的返回值是否有资格进行复制省略?
修改
我在某种程度上改变了这个例子。在Debug和Release版本中使用Visual Studio 2017,我在return语句中看到了一个复制结构。希望这只是因为我弄乱了Type
,这有助于我进行调试。
#include <map>
#include <string>
#include <iostream>
#include <ostream>
struct Type
{
Type()
{
std::cout << "Default construction\n";
};
explicit Type(std::string obj) : obj(std::move(obj))
{
std::cout << "Other construction\n";
}
~Type() = default;
Type(const Type& other) : obj{other.obj}
{
std::cout << "Copy construction\n";
}
Type(Type&& other) noexcept : obj{std::move(other.obj)}
{
std::cout << "Move constructor\n";
}
Type& operator=(const Type& other)
{
std::cout << "Copy assignment\n";
if (this == &other)
return *this;
obj = other.obj;
return *this;
}
Type& operator=(Type&& other) noexcept
{
std::cout << "Move assignment\n";
if (this == &other)
return *this;
obj = std::move(other.obj);
return *this;
}
friend std::ostream& operator<<(std::ostream& os, const Type& obj1)
{
return os << obj1.obj;
}
std::string obj;
};
std::map<std::string, Type> mVariables;
Type Get(const std::string& variableName)
{
const auto variableIt = mVariables.find(variableName);
if(variableIt==std::end(mVariables)) {
throw std::runtime_error("Unknown variable requested.");
}
return variableIt->second;
}
int main()
{
mVariables.emplace(std::make_pair("key", Type("value")));
const auto value = Get("key");
std::cout << value;
return 0;
}
上面的示例提供了以下输出,它提出了一些关于make_pair
的问题,但这不是讨论。我想我的困惑是,在这个例子中是什么阻止了复制省略?
Other construction
Move constructor
Move constructor
Copy construction
value
答案 0 :(得分:0)
我建议您在Compiler Explorer上播放。
我在GCC主干中看到的几乎所有的map和字符串函数都被内联,剩下的唯一函数调用是memcmp
(用于内联查找和字符串比较)和{{1} }和new
(对于返回的副本)。
memcpy
仍然存在,但仍然只有一次致电map::find
和new
。
答案 1 :(得分:0)
在C ++ 17 1 之前,const auto value = Get("key");
的语义是从返回的表达式variableIt->second
复制初始化临时对象,然后复制初始化{{1}来自临时对象。所以最初基本上有两个副本/动作。
根据N3797 [class.copy]第31段子弹3:
从value
直接构建value
,可以省略临时对象的复制/移动。
- 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过直接构造临时对象来省略复制/移动操作进入省略副本/移动的目标。
通过阻止临时对象在语义中实现,从C ++ 17开始保证此副本。 variableIt->second
的新语义从const auto value = Get("key");
2 变为初始化value
。
来自variableIt->second
的副本无法,因为它不符合variableIt->second
声明中出现的复制省略要求,即N3797 [等级。复制]第31段子弹1 3 :
- 在具有类返回类型的函数中的
return
语句中,当表达式为非易失性自动对象的名称时(函数或catch子句参数除外) )使用与函数返回类型相同的cv-unqualified类型,通过将自动对象直接构造为函数的返回值,可以省略复制/移动操作
这是合理的,因为return
的生命周期未结束且可能在将来使用,因此variableIt->second
无法作为value
的别名进行优化,因此需要复制。
1 自C ++ 17以来唯一的区别是第三段中提到的保证副本省略。在我看来,从C ++ 17之前的语义开始分析是更为直接的。
2 在C ++ 17中有几个规则结合起来得出这个结论,对于这个问题并不重要,所以我从标准中发出相应规则的引用。 子>
3 此规则的措辞在C ++ 17中有所改变,而规则基本相同。