复制映射迭代器对值的省略
Copy elision of map iterator pair value
在下面的 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
(用于内联查找和字符串比较)和new
和memcpy
(用于返回的副本)。
叮当树干似乎没有那么多内联。map::find
仍然存在,但仍然只是一个电话new
和memcpy
。
- 错误 C2760:语法错误:映射迭代器上意外的标记"标识符",预期的";"
- 映射迭代器与运算符不匹配
- 我可以将映射迭代器与 OpenMP 并行使用吗?
- 取消引用映射迭代器时返回对临时的引用
- 为什么映射迭代器显示基本操作数无效错误
- 复制映射迭代器对值的省略
- ->第一个/第二个到空映射迭代器开始
- 由引用集/映射迭代器传递的迭代器不可取消引用
- 在 GCC 上自动声明映射迭代器错误
- 指向同一项的两个映射迭代器是否保证相等
- 嵌套映射迭代器
- 模板标准::映射::迭代器实例化
- 映射迭代器遍历技术
- 映射迭代器与手动
- VC++11映射和多映射迭代器(重载)C2535
- 切换情况下映射迭代器的交叉初始化
- C++-映射迭代器中的if语句导致程序崩溃的原因
- 映射迭代器:一元'*'的无效类型参数(具有"int")
- 比较未引用的映射迭代器(std::pairs):C2678
- 与C++中擦除映射迭代器的行为不一致