43

Effective C++一书中,我看到了以下段落:

结果,如果你写

class Empty{};

它基本上和你写的一样:

class Empty {
public:
    Empty() { ... }
    Empty(const Empty& rhs) { ... }
    ~Empty() { ... }
    Empty& operator=(const Empty& rhs) { ... } // copy assignment operator
};

以下代码将导致生成每个函数:

Empty e1;
Empty e2(e1);
e2 = e1;

但是在反汇编上面的代码生成的可执行文件后,我意识到不是这样:没有任何函数被调用。

这是主要的汇编代码:

00000000004006cd <main>:
  4006cd:       55                      push   %rbp
  4006ce:       48 89 e5                mov    %rsp,%rbp
  4006d1:       b8 00 00 00 00          mov    $0x0,%eax
  4006d6:       5d                      pop    %rbp
  4006d7:       c3                      retq 

.text段中没有任何名为“Empty”的函数。

那么在我们调用构造函数或空类赋值之后,编译器的行为究竟是什么?它是否像书中所说的那样产生一些功能?如果是这样,它们存储在哪里?

4

3 回答 3

54

函数存在,但可以内联。

当编译器内联函数时,它意识到它们是无操作的,并且没有生成代码。

本书所说的在某种程度上是正确的,概念函数是由编译器创建的,用于内联和直接调用。

但是生成的代码是空的,因此优化编译器将删除该函数的任何证据(设置一个 this 指针),并且永远不会直接调用这些函数。

这本书并不是真正试图解释生成的代码,而是创建一个类的影响,以及它为正常操作而生成的“隐藏”函数。

于 2016-08-23T06:26:26.283 回答
21

这些方法确实是为类生成的,但它们是作为“内联”生成的。

因为它们是逐个成员的实现(例如,复制构造函数将复制构造所有成员),所以当它们class为空时,它们实际上什么也没做,并且内联它们只是不可见的。

然而,记住这些方法会自动获得实现是非常重要的......例如代码

struct Foo {
    char *buf;
    Foo() : buf(new char[10]) {}
    ~Foo() { delete[] buf; }
};

是错误的,因为复制构造函数和赋值的自动生成代码是错误的,会导致缓冲区的多次删除。

错误不是因为已经写过的东西,而是因为没有写过的东西,很棘手。这就是为什么记住 C++ 会自动为您编写的内容非常重要的原因:如果该实现是您想要的,那么就完美,但如果不是,则通过提供正确的实现或禁止创建或使用错误的代码来修复它。

于 2016-08-23T06:27:45.627 回答
9

你和这本书是从不同的抽象层次来面对这种情况的。

本书使用术语“生成”来指代由编译器隐式定义到抽象 C++ 程序中的 C++ 函数。这绝对会发生。

您将其解释为在翻译程序中实际生成实际机器代码。这不是它的意思。只要保持原始抽象程序的语义,实际机器代码的生成总是受制于编译器的突发奇想。

因此,这本书当然没有错,尽管为了清楚起见,我可能会使用不同的词。坚持标准术语永远不会受到伤害。

于 2016-08-23T15:04:35.193 回答