C++ - 为什么在没有明显的构造函数匹配时编译此代码?

C++ - why does this code compile when there's no obvious constructor match?

本文关键字:编译 代码 构造函数 为什么 C++      更新时间:2023-10-16

请考虑以下代码:

class Foo {
 public:
  explicit Foo(double) {}
};
Foo * test();
Foo * test() {
  return new Foo(Foo(1.0));   // (1)
}

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

  return new Foo(1.0);
有趣的是,这个变化似乎也编译了无警告:
  return new Foo(Foo(Foo(Foo(1.0))));

为什么使用clang编译这些示例时没有警告,甚至使用-Wall -Weverything标志?为什么Foo::Foo(double)接受Foo的实例作为有效的double参数?这是操作员的某种特殊行为吗?

我的原始代码在一个更大的上下文中,并使用两个基于llvm -3的编译器进行了测试。编译时都没有警告或错误。有了一个,代码实际上按照我的预期运行,事实上我有一段时间没有意识到有一个bug。另一方面,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)); }

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

如果不希望自动生成复制构造函数,通常的方法是将它们定义为已删除或私有。

还要注意,编译器在某些情况下有权从程序中删除整个对象,尽管通常情况下它不应该这样做。如果你在Foo构造函数中使用指向Foo的指针集合,你必须在所有的构造函数和析构函数中严格地处理它们,这意味着你必须编写自己的复制/移动构造函数、析构函数和赋值操作符,否则当编译器省略一个对象时,你将会失败。

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

隐式声明的复制构造函数的特定签名取决于你的类定义,即基类和成员—它的格式通常为X::X(X const &),但必要时也可以是X::X(X &)X::X(X volatile &)。异常规范是尽可能严格的。隐式声明的复制构造函数的定义通常由复制基和成员组成,但在某些情况下(例如,声明move构造函数),它可以定义为删除。

(在以后的c++版本中,如果声明了copy或move赋值操作符,则隐式声明的复制构造函数将被定义为deleted。在c++ 11和c++ 14中,它是默认的。)