C ++意外行为(我的临时演员在哪里!?)

时间:2013-08-28 11:28:22

标签: c++

这是一个r值实验,但当gcc向我抱怨缺少move-constructor(我已删除它)并且没有回复到复制构造函数时(如我所料),它发生了变异 然后我从标志中删除了-std = c ++ 11并尝试了下面看到的内容,它有很多输出(最初没有),因为我试图弄清楚为什么它不起作用(我知道)如何调试,但我发现stdout上的消息是发生事情的一个很好的指标)

这是我的代码:

#include <iostream>

class Object {
public:
    Object() { id=nextId; std::cout << "Creating object: "<<id<<"\n"; nextId++; }
    Object(const Object& from) {
         id=nextId; std::cout << "Creating object: "<<id<<"\n"; nextId++;
        std::cout<<"(Object: "<<id<<" created from Object: "<<from.id<<")\n";
    }
    Object& operator=(const Object& from) {
        std::cout<<"Assigning to "<<id<<" from "<<from.id<<"\n";
        return *this;
    }
    ~Object() { std::cout<<"Deconstructing object: "<<id<<"\n";}

private:
    static int nextId;
    int id;
};

int Object::nextId = 0;

Object test();

int main(int,char**) {
    Object a;
    std::cout<<"A ought to exist\n";
    Object b(test());
    std::cout<<"B ought to exist\n";
    Object c = test();
    std::cout<<"C ought to exist\n";
    return 0;
}


Object test() {
    std::cout<<"In test\n";
    Object tmp;
    std::cout<<"Test's tmp ought to exist\n";
    return tmp;
}

输出:

Creating object: 0
A ought to exist
In test
Creating object: 1
Test's tmp ought to exist
B ought to exist
In test
Creating object: 2
Test's tmp ought to exist
C ought to exist
Deconstructing object: 2
Deconstructing object: 1
Deconstructing object: 0

我使用解构,因为解构已经是一个词,有时候我使用析构函数,我对这个词一点都不满意,我赞成析构函数作为名词。

这就是我的预期:

A to be constructed
tmp in test to be constructed, a temporary to be created from that 
    tmp, tmp to be destructed(?) 
that temporary to be the argument to B's copy constructor
the temporary to be destructed.
C's default constructor to be used
"" with a temporary from `test`
C's assignment operator to be used
the temporary to be destructed
c,b,a to be destructed.

我被称为“顽固的C”,我试图学习使用C ++而不是“C with namespaces”。

有人可能会说“编译器将其优化出来”我希望那个人现在或永远不会用这样的答案回答问题,优化不能改变程序状态,它必须就好像一切都按照规范说的发生了,所以编译器可能会通过在cout上添加包含数字的消息来幽默我,它可能不会费心甚至增加数字等等,但程序的输出将是相同的,如果它确实完成了代码描述的所有内容。

所以这不是优化,发生了什么?

4 个答案:

答案 0 :(得分:5)

这是一种优化,是唯一允许改变程序可观察行为的优化。

这是段落12.8./31,取自标准草案n3337(强调我的):

  

当满足某些条件时,允许实现省略类的复制/移动构造   对象,即使对象的复制/移动构造函数和/或析构函数有副作用。在这种情况下,   该实现将省略的复制/移动操作的源和目标视为两个不同的   引用相同对象的方式,以及对象的破坏发生在时间的晚期   当两个对象在没有优化的情况下被销毁的时候。   这种复制/移动的省略   在以下情况下允许称为 copy elision 的操作(可以合并到   消除多份副本):

     
    

- 在具有类返回类型的函数的return语句中,当表达式是a的名称时     非易失性自动对象(函数或catch子句参数除外)具有相同的cv-     unquali fi ed type作为函数返回类型,可以通过构造省略复制/移动操作     自动对象直接进入函数的返回值

         

- 在throw-expression中,当操作数是非易失性自动对象的名称时(除了     function或catch-clause参数),其范围不超出最内层的末尾     封闭try-block(如果有的话),从操作数到异常的复制/移动操作     通过将自动对象直接构造到异常对象

中,可以省略object(15.1)          

- 当复制/移动尚未绑定到引用(12.2)的临时类对象时     对于具有相同cv-unquali fi ed类型的类对象,可以省略复制/移动操作     将临时对象直接构造到省略的复制/移动

的目标中          

- 当异常处理程序的异常声明(第15节)声明一个相同类型的对象时     (除了cv-quali fi cation)作为异常对象(15.1),可以省略复制/移动操作     通过将异常声明视为异常对象的别名(如果程序的含义)     除了为声明的对象执行构造函数和析构函数之外,它将保持不变     异常声明。

  
     

[示例...省略]

复制/移动构造函数的语义就是这样,复制/移动对象的内容,同时初始化另一个对象。如果您的复制构造函数向您的生日聚会发送带有邀请的电子邮件,那么如果您最终单独参加聚会,您不应该感到惊讶:)

好的,一些复制构造函数也做其他事情。想想智能指针的引用计数。但是,如果它被优化掉了,那很好。没有副本,也没有必要计算。

答案 1 :(得分:3)

我相信你正在体验Copy Elision。因此,是的,这是优化。

http://en.wikipedia.org/wiki/Copy_elision

  

在C ++计算机编程中,copy elision指的是编译器   消除不必要的对象复制的优化技术。   C ++语言标准通常允许实现执行   任何优化,提供结果程序的可观察行为   如果,即假装,程序是完全执行的是相同的   按照标准的要求。

     

该标准还描述了一些可以进行复制的情况   消除即使这会改变程序的行为,也是最强的   常见的是返回值优化。

重点是我的。

答案 2 :(得分:2)

由于复制/移动临时对象具有成本,因此即使相应的构造函数或析构函数具有副作用,也明确允许编译器忽略临时对象。复制/移动省略通常不被视为优化,大多数编译器甚至在调试模式下也不会构建临时对象(这是合理的,因为您不希望在调试和优化构建之间有不同的行为)。

C ++ 11标准中的相关条款是12.8 [class.copy]第31段:

  

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用。 ...

允许复制/移动省略的情况是:

  • in return statements
  • in throw表达式
  • 当临时未绑定参考时
  • catch子句

确切的规则有一些额外的条件。

答案 3 :(得分:0)

要防止复制错觉,请使用复制/交换算法实现赋值运算符,如:

Object &operator =(Object other)
{
    std::swap(*this, other);
    return *this;
}

然后尝试:

Object a;
a = test();

这样,编译器在将对象传递给赋值运算符时将调用复制(或移动)ctor。