复制初始化的奇怪行为,不会调用copy-constructor!

时间:2011-05-28 16:56:52

标签: c++ initialization copy-constructor copy-initialization

我正在阅读直接初始化和复制初始化(§8.5/ 12)之间的区别:

T x(a);  //direct-initialization
T y = a; //copy-initialization

我从阅读有关copy-initialization的内容中了解到,它需要accessible & non-explicit copy-constructor,否则程序将无法编译。我通过编写以下代码验证了它:

struct A
{
   int i;
       A(int i) : i(i) { std::cout << " A(int i)" << std::endl; }
   private:
       A(const A &a)  {  std::cout << " A(const A &)" << std::endl; }
};

int main() {
        A a = 10; //error - copy-ctor is private!
}

GCC提出错误(ideone)说:

  

prog.cpp:8:错误:'A :: A(const A&amp;)'是私有的

到目前为止一切正常,reaffirming what Herb Sutter says

  

复制初始化意味着在必要时首次调用用户定义的转换后,使用复制构造函数初始化对象,并且等效于“T t = u;”形式:


之后,我通过评论private关键字来访问copy-ctor。现在,我自然希望以下内容得到印刷:

  

A(const A&amp;)

但令我惊讶的是,它打印了这个(ideone):

  

A(int i)

为什么?

好的,我了解首先使用A 10创建int类型的临时对象,使用A(int i),将转换规则应用为它需要在这里(§8.5/ 14),然后它应该调用copy-ctor来初始化a。但事实并非如此。为什么?

如果允许实现消除调用copy-constructor(第8.5 / 14节)的需要,那么在复制构造函数被声明为private时为什么不接受代码?毕竟,它没有称之为。它就像一个被宠坏的孩子,他第一次恼怒地要求一个特定的玩具,当你给他一个特定的一个时,他把它丢在背后。 :|

这种行为会有危险吗?我的意思是,我可能会在copy-ctor中做一些其他有用的事情,但是如果它没有调用它,那么它是否会改变程序的行为?

6 个答案:

答案 0 :(得分:10)

您是在问为什么编译器会进行访问检查? 12.8 / 14 in C ++ 03:

  

如果副本,程序格式不正确   构造函数或复制赋值   对象的运算符是隐式的   使用和特殊成员功能   无法访问

当实现“省略复制结构”(由12.8 / 15允许)时,我不认为这意味着复制文件不再“隐式使用”,它只是不执行。

或者你在问为什么标准会这么说?如果复制省略是关于访问检查的此规则的一个例外,那么您的程序将在成功执行省略的实现中形成良好的形式,但在不实现的实现中格式不正确。

我很确定作者会认为这是一件坏事。当然,以这种方式编写可移植代码更容易 - 编译器会告诉您是否编写了尝试复制不可复制对象的代码,即使在您的实现中省略了副本。我怀疑实施者在检查访问之前判断优化是否会成功,或者在尝试优化之后推迟访问检查,这也可能会给实施者带来不便,尽管我不知道是否保证考虑。

  

这种行为会有危险吗?一世   意思是,我可能会做一些有用的事情   副本中的东西,但如果它   不称之为,然后不这样做   改变程序的行为?

当然它可能很危险 - 当且仅当对象被实际复制时才会出现副本构造函数中的副作用,并且应该相应地设计它们:标准说副本可以省略,所以不要将代码放入复制构造函数,除非你很高兴在12.8 / 15中定义的条件下被删除:

MyObject(const MyObject &other) {
    std::cout << "copy " << (void*)(&other) << " to " << (void*)this << "\n"; // OK
    std::cout << "object returned from function\n"; // dangerous: if the copy is
      // elided then an object will be returned but you won't see the message.
}

答案 1 :(得分:5)

C ++显式允许几个涉及实际更改程序语义的复制构造函数的优化。 (这与大多数优化形成对比,这些优化不会影响程序的语义)。特别是,有几种情况允许编译器重新使用现有对象,而不是复制一个,如果它知道现有对象将无法访问。这种(复制结构)就是这样一种情况;另一个类似的情况是“返回值优化”(RVO),如果你声明保存函数返回值的变量,那么C ++可以选择在调用者的帧上分配它,这样它就不需要了在函数完成时将其复制回调用者。

一般情况下,在C ++中,如果您定义了一个副作用或除了复制之外的其他任何操作,那么您正在玩火。

答案 2 :(得分:4)

在任何编译器中,语法[和语义]分析过程都是在代码优化过程之前完成的。

代码必须在语法上有效,否则甚至不会编译。它仅在后期阶段(即代码优化)中,编译器决定忽略它创建的临时阶段。

所以你需要一个可访问的副本c-tor。

答案 3 :(得分:1)

Here你可以找到这个(带有你的评论;)):

  [标准]也说临时副本   可以省略,但语义   约束(例如,可访问性)   复制构造函数仍然必须   检查。

答案 4 :(得分:0)

RVO和NRVO,哥们。复制椭圆的完美案例。

答案 5 :(得分:0)

这是编译器的优化。

评估时:A a = 10;代替:

  1. 首先通过A(int);

  2. 构建临时对象
  3. 通过复制构造函数构建a并传入临时文件;

  4. 编译器只需使用a构建A(int)