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

时间:2015-09-03 08:18:08

标签: c++ c++11 const copy-constructor

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

#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())我需要确保没有修改返回的将生成对象,因为返回的对象是浅拷贝,浅拷贝的修改将影响“父”对象(可能是常量)。

2 个答案:

答案 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;

Live Demo