构造函数混淆

Constructor confusion

本文关键字:构造函数      更新时间:2023-10-16

我一直认为我非常了解C++,但有时我甚至对最基本的东西感到惊讶。

在以下场景中,我对调用构造函数Derived::Derived(const Base&)的原因感到困惑:

class Base
{ };
class Derived : public Base
{
    public:
    Derived() { }
    Derived(const Base& b) 
    {
        std::cout << "Called Derived::Derived(const Base& b)" << std::endl;
    }
};
int main()
{
    Derived d;
    Base b;
    d = b;
}

它输出:Called Derived::Derived(const Base& b),表示调用了Derived中的第二个构造函数。现在,我认为我非常了解C++,但我不明白为什么要调用那个构造函数。我理解整个"四条规则"概念,我认为表达式d = b可以做两件事之一:要么1)调用Base的隐式(编译器生成的)赋值运算符,要么2)触发编译器错误,抱怨函数Derived& operator = (const Base&)不存在。

相反,它调用了构造函数,即使表达式d = b是赋值表达式。

那么为什么会发生这种情况呢?

d=b可能发生,因为b已转换为Derived。第二个构造函数用于自动类型转换。这就像d=(派生)b

Derived是Base,但Base不是Derived,因此必须在赋值前对其进行转换。

将基分配给派生?也许你指的是参考文献(b)中的(a),或者衍生自base。这实际上没有意义,但编译器正确地使用了您的(非显式)构造函数将Base实例转换为新的Derived实例(随后将其赋值为d)。

使用显式构造函数可以防止这种情况自动发生。

就我个人而言,我认为您把代码示例搞砸了,因为如果没有转换

,通常将第一类基类分配给派生类是没有意义的

这里有两个交互功能:

  • 赋值运算符从不继承
  • 非显式构造函数或转换运算符(operator T())定义了可以隐式用作转换序列一部分的用户转换

分配运算符从不继承

一个简单的代码示例:

struct Base {}; // implicitly declares operator=(Base const&);
struct Derived: Base {}; // implicitly declares operator=(Derived const&);
int main() {
  Derived d;
  Base b;
  d = b; // fails
}

来自视频:

prog.cpp: In function ‘int main()’:
prog.cpp:7: error: no match for ‘operator=’ in ‘d = b’
prog.cpp:2: note: candidates are: Derived& Derived::operator=(const Derived&)

转换序列

每当出现"阻抗"不匹配时,例如:

  • Derived::operator=需要Derived const&参数
  • 提供CCD_ 11

编译器将尝试建立一个转换序列来弥补这一差距。这样的转换序列最多可包含一个用户定义的转换。

在这里,它将寻找:

  • 可以用Base&调用的Derived的任何构造函数(非显式)
  • Base中会产生Derived项的转换运算符

没有Base::operator Derived(),但有一个Derived::Derived(Base const&)构造函数。

因此,我们的转换序列是为我们定义的:

  • Base&
  • Base const&(琐碎)
  • Derived(使用Derived::Derived(Base const&)
  • Derived const&(绑定到常量引用的临时对象)

然后调用CCD_ 23。

正在运行

如果我们用更多的跟踪来扩充代码,我们可以看到它在运行。

#include <iostream>
struct Base {}; // implicitly declares Base& operator(Base const&);
struct Derived: Base {
  Derived() {}
  Derived(Base const&) { std::cout << "Derived::Derived(Base const&)n"; }
  Derived& operator=(Derived const&) {
    std::cout << "Derived::operator=(Derived const&)n";
    return *this;
  }
};
int main() {
  Derived d;
  Base b;
  d = b;
}

哪个输出:

Derived::Derived(Base const&)
Derived::operator=(Derived const&)

注意:防止这种情况发生

在C++中,可以删除转换序列中使用的构造函数。为此,需要使用explicit关键字作为构造函数声明的前缀。

在C++0x中,也可以在转换运算符(operator T())上使用此关键字。

如果在这里我们在Derived::Derived(Base const&)之前使用explicit,那么代码就会变得格式错误,应该被编译器拒绝。

由于您已经为Derived定义了一个采用Base类型的构造函数,并且您正在向下转换Base,因此编译器会为向上转换选择最合适的构造函数,在本例中,它就是您定义的Dervied(const Base&b)。如果您没有定义此构造函数,那么在尝试进行赋值时,实际上会出现编译错误。有关更多信息,您可以在Linuxtopia上阅读以下内容。

它不能分配不同类型的值,所以它应该首先构造一个Derived临时值。