我发现代码的奇怪行为显然忽略了常量:
#include <iostream>
using std::cerr;
class A
{
public:
A() { cerr << "A::A()\n"; }
A(const A &a) { cerr << "A::A(const A&)\n"; }
A(A &&) { cerr << "A::A(A&&)\n"; }
A & operator = (const A &a) { cerr << "A::operator=(const A&)\n"; return *this; }
A & operator = (A &&a) { cerr << "A::operator(A&&)\n"; return *this; }
~A() { cerr << "A::~A()\n"; }
const A get() const { cerr << "const A A::get() const\n"; return A(); }
A get() { cerr << "A A::get()\n"; return A(); }
};
int main()
{
const A a;
A b = a.get();
}
首先,我在这里期待的是:a
是一个常量,因此调用了get()的常量版本。接下来,返回常量对象,但左侧是非常量对象b
,因此应该调用复制构造函数。不是:
A::A()
const A A::get() const
A::A()
A::~A()
A::~A()
这种行为是否符合c ++标准? 那么,RVO是否可以忽略临时对象的常量?如何在这里强制执行复制?
禁用了copy-elision的输出(-fno-elide-constructors
)进行了额外的移动和预期的复制构造函数调用:
A::A()
const A A::light_copy() const
A::A()
A::A(A&&)
A::~A()
A::A(const A&)
A::~A()
A::~A()
A::~A()
如果a
对象不是常量,那么它将是两次移动而不复制,这也是预期的。
PS。这个行为对我来说很重要,因为我看到的是打破浅层复制的const-strictness:对于get()
的const-version(实际上是shallow_copy()
)我需要确保没有修改返回的将生成对象,因为返回的对象是浅拷贝,浅拷贝的修改将影响“父”对象(可能是常量)。
答案 0 :(得分:7)
那么,RVO是否可以忽略临时对象的常量?
是。 [class.copy] / p31(引用N4527,其中包含一些澄清意图的DR;强调我的):
复制/移动操作的省略,称为复制省略,是 在下列情况下允许(可以合并到 消除多份副本):
- 在具有类返回类型的函数中的
return
语句中,当表达式是非易失性自动对象的名称时(其他 比函数参数或由...引入的变量 处理程序的异常声明 (15.3))具有相同类型(忽略cv-qualification)作为函数返回类型,副本/移动 通过直接构造自动对象可以省略操作 进入函数的返回值- [...]
- 当未绑定到引用(12.2)的临时类对象被复制/移动到具有相同类型的类对象时 (忽略cv-qualification),可以省略复制/移动操作 将临时对象直接构造到目标中 省略了复制/移动
- [...]
第三个子弹是适用的;请注意,类似的规则也适用于NRVO(第一颗子弹)。
答案 1 :(得分:1)
如果您想禁止const临时构造/分配,您可以将这些方法标记为已删除:
A(const A &&) = delete;
A& operator = (const A &&) = delete;