5

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

这是我的代码:

#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

4 回答 4

5

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

这是12.8./31从标准草案 n3337(强调我的)中摘录的段落:

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的较晚时间。没有优化就被破坏了。这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以组合起来消除多个副本):

— 在具有类返回类型的函数的 return 语句中,当表达式是具有与函数返回类型相同的 cv 非限定类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时,可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作

— 在 throw 表达式中,当操作数是非易失性自动对象(函数或 catch 子句参数除外)的名称时,其范围不超出最里面的封闭 try 块的末尾(如果有一)、从操作数到异常对象(15.1)的复制/移动操作可以通过将自动对象直接构造到异常对象中来省略

— 当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同 cv 非限定类型的类对象时,可以通过将临时对象直接构造到省略复制/移动的目标

— 当异常处理程序的异常声明(第 15 条)声明了与异常对象(15.1)相同类型的对象(cv 限定除外)时,可以通过处理异常声明来省略复制/移动操作作为异常对象的别名,如果程序的含义将保持不变,除了为异常声明声明的对象执行构造函数和析构函数。

[示例...省略]

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

好的,一些复制构造函数也做其他事情。想想智能指针的引用计数。但是,如果它得到优化,那就没问题了。没有副本,也不需要计算任何内容。

于 2013-08-28T11:46:51.523 回答
4

我相信您正在经历复制省略。因此,是的,它是优化。

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

在 C++ 计算机编程中,复制省略是指消除不必要的对象复制的编译器优化技术。C++ 语言标准通常允许实现执行任何优化,前提是生成的程序的可观察行为与好像(即假装)程序完全按照标准的要求执行。

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

重点是我的。

于 2013-08-28T11:36:30.943 回答
3

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

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

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

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

  • 在回报声明
  • throw表达式中
  • 当临时对象未绑定到引用时
  • catch条款中

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

于 2013-08-28T11:51:08.343 回答
0

为了防止复制错误,使用复制/交换算法实现赋值运算符,例如:

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

然后尝试:

Object a;
a = test();

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

于 2013-08-28T11:50:32.427 回答