返回常量对象并将其分配给非常量对象
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;
我发现代码的奇怪行为显然忽略了常量性:
#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;