返回常量对象并将其分配给非常量对象

returning constant object and assigning it to non-constant object

我发现代码的奇怪行为显然忽略了常量性:

#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 可以简单地忽略临时对象的常量性吗?以及如何在此处强制执行复制?

禁用复制省略的输出 (-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。这种行为对我来说很重要,因为我看到的是打破浅层复制常量严格性:对于 get() 的常量版本(实际上是 shallow_copy())我需要确保没有修改返回的将创建对象,因为返回的对象是浅拷贝,对浅拷贝的修改将影响 "parent" 对象(可能是常量)。

So, is it okay that constness of a temporary object is simply ignored by RVO?

是的。 [class.copy]/p31(引用 N4527,其中包含一些阐明意图的 DR;强调我的):

This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

  • in a return statement in a function with a class return type, when the expression is the name of a nonvolatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler (15.3)) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value
  • [...]
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same type (ignoring cv-qualification), the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
  • [...]

第三点适用于此;请注意,类似的规则也适用于 NRVO(第一个项目符号)。

如果你想禁止 construction/assignation 来自 const temporary,你可以将这些方法标记为已删除:

A(const A &&) = delete;
A& operator = (const A &&) = delete;

Live Demo