复制映射迭代器对值的省略

Copy elision of map iterator pair value

本文关键字:映射 迭代器 复制      更新时间:2023-10-16

在下面的 MVE 中,Get函数的返回值是否符合复制省略的条件?

编辑

我稍微改变了这个例子。在调试和发布版本中使用 Visual Studio 2017,我在 return 语句上看到了复制构造。希望这只是因为我搞砸了帮助我进行调试的Type

#include <map>
#include <string>
#include <iostream>
#include <ostream>
struct Type
{
Type()
{
std::cout << "Default constructionn";
};
explicit Type(std::string obj) : obj(std::move(obj))
{
std::cout << "Other constructionn";
}
~Type() = default;
Type(const Type& other) : obj{other.obj}
{
std::cout << "Copy constructionn";
}
Type(Type&& other) noexcept : obj{std::move(other.obj)}
{
std::cout << "Move constructorn";
}
Type& operator=(const Type& other)
{
std::cout << "Copy assignmentn";
if (this == &other)
return *this;
obj = other.obj;
return *this;
}
Type& operator=(Type&& other) noexcept
{
std::cout << "Move assignmentn";
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

在 C++171之前,const auto value = Get("key");的语义是从返回的表达式variableIt->second复制初始化临时对象,然后从临时对象复制初始化value。所以最初基本上有两个副本/移动。

根据N3797 [class.copy] 第 31 段第 31 点 3,可以通过直接从variableIt->second构造value来省略临时对象的复制/移动:

  • 当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同 CV-UNQUALIFIED 类型的类对象时,可以通过将临时对象直接构造到省略的复制/移动的目标中来省略复制/移动操作。

自 C++17 以来,通过防止临时对象在语义中具体化来保证此副本。const auto value = Get("key");的新语义从第2variableIt->second变为复制初始化value

variableIt->second的副本不能省略,因为它不符合return语句中出现的副本省略的要求,即 N3797 [class.copy] 第 31 段第 1点 3

  • 在具有类返回类型的函数的return语句中,当表达式是与函数返回类型具有相同 CV-unqualified 类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时,可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作

这是合理的,因为variableIt->second的生命周期不会结束,并且可能会在将来使用,因此value不能优化为variableIt->second的别名,因此需要副本。


1自 C++17 以来的唯一区别是第三段中提到的保证复制省略。从 C++17 之前的语义开始分析更直接(在我看来)。

2在 C++17 中,有几个规则组合得出了这个结论,对于这个问题来说并不重要,所以我从标准中引用了相应的规则。

3这条规则的措辞在第17 C++中略有改动,而规则基本上是相同的。

我建议你在编译器资源管理器上玩一玩。

我在 GCC 主干中看到的是,几乎所有的映射和字符串函数都是内联的,唯一剩下的函数调用是memcmp(用于内联查找和字符串比较)和newmemcpy(用于返回的副本)。

叮当树干似乎没有那么多内联。map::find仍然存在,但仍然只是一个电话newmemcpy