2

请考虑以下代码:

class Foo {
 public:
  explicit Foo(double) {}
};

Foo * test();
Foo * test() {
  return new Foo(Foo(1.0));   // (1)
}

我的问题涉及第 (1) 行。这与我花了一些时间才找到的错误非常相似。由于复制/粘贴错误,我没有注意到该类型被指定了两次。正确的行显然是:

  return new Foo(1.0);

有趣的是,此更改似乎也可以无警告地编译:

  return new Foo(Foo(Foo(Foo(1.0))));

为什么这些示例编译时没有使用 clang 的警告,即使使用了-Wall -Weverything标志?为什么Foo::Foo(double)接受 Foo 的实例作为有效double参数?这是 operator new 的一些特殊行为吗?

我的原始代码位于更大的上下文中,并使用两个基于 LLVM-3 的编译器进行了测试。两者都在没有警告或错误的情况下编译。有了一个,代码实际上按我的预期运行,事实上我有一段时间没有意识到有一个错误。另一方面, Foo 的实例表现得非常奇怪 - 我无法正确描述它 - 就好像返回指针的后续副本“神奇地”变成了与原始值不同的值,导致两个合作之间的状态不匹配应该持有指向共享 Foo 的等效指针的对象,但由于某种原因在分配后持有不同的值。直到我明白这里发生了什么,这似乎真的很奇怪!

有趣的是,以下两种编译器都可以编译:

class Foo { public: explicit Foo(double) {} };
class Bar { public: explicit Bar(double) {} };

Foo * testFoo() { return new Foo(Foo(1.0)); }
Bar * testBar() { return new Bar(Bar(1.0)); }

但以下版本没有:

Foo * testFooBar() { return new Foo(Bar(1.0)); }
4

2 回答 2

7

编译器会自动生成一个复制构造函数Foo(const Foo&),并且可能还会根据确切的版本/设置移动构造函数Foo(Foo&&)。这与 operator new 或任何指针魔术无关。您的代码只需调用编译器为您定义的完全正常的复制/移动构造函数。就这样。此行为是标准规定的。不可复制的类(至少在标准的原始版本中)实际上是毫无价值的。

如果您不想自动生成复制构造函数,通常的技术是将它们定义为已删除或私有。

另请注意,在某些情况下,编译器有权从程序中实际删除整个对象,即使通常它不应该这样做。如果您在 Foo 构造函数中使用指向 Foo 的指针进行 hijink,则必须在所有构造函数和析构函数中严格处理它们,这意味着必须编写自己的复制/移动构造函数、析构函数和赋值运算符,否则你会来编译器省略对象时的裁剪器。

于 2014-08-02T10:55:05.757 回答
2

您正在观察的构造函数是复制构造函数。每个类都有一个复制构造函数。如果您没有在类定义中声明任何复制构造函数,则会为您隐式声明一个复制构造函数。

隐式声明的复制构造函数的特定签名取决于您的类定义,即基类和成员 - 通常是形式X::X(X const &),但它可能是X::X(X &)X::X(X volatile &)如果需要。异常规范尽可能严格。隐式声明的复制构造函数的定义通常由复制基和成员组成,但在某些情况下它可能被定义为删除(例如,如果您声明了移动构造函数)。

(在未来的 C++ 版本中,如果声明复制或移动赋值运算符,则隐式声明的复制构造函数将被定义为已删除。在 C++11 和 C++14 中,它是默认值。)

于 2014-08-02T11:19:29.697 回答