构造函数混乱

时间:2011-05-28 17:31:16

标签: c++ constructor

我一直认为我对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)调用{{1}的隐式(编译器生成的)赋值运算符}或2)触发编译器错误,抱怨函数Base不存在。

相反,它调用构造函数,即使表达式Derived& operator = (const Base&)是赋值表达式。

那么为什么会这样呢?

5 个答案:

答案 0 :(得分:11)

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

派生isa Base,但Base不是派生的,因此必须在分配之前进行转换。

答案 1 :(得分:6)

将基础分配给派生?也许你的意思是(a)通过ref(b)或派生到base。这没有用,但编译器正确使用您的(非显式)构造函数将Base实例转换为新的Derived实例(随后将其分配到d)。

使用explicut构造函数来防止这种情况自动发生。

就我个人而言,我认为你搞砸了你的代码示例,因为通常将firstclass base赋给派生没有转换是没有意义的

答案 2 :(得分:5)

这里有两个相互作用的功能:

  • 分配运算符永远不会被继承
  • 非显式的构造函数或转换运算符(operator T())定义可以隐式使用的转换序列的用户转换

Assignement Operators永远不会被继承

一个简单的代码示例:

struct Base {}; // implicitly declares operator=(Base const&);
struct Derived: Base {}; // implicitly declares operator=(Derived const&);

int main() {
  Derived d;
  Base b;
  d = b; // fails
}

来自ideone

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&)

转化顺序

每当有阻抗&#34;不匹配,例如:

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

编译器将尝试建立转换序列以弥补差距。此类转化序列可能包含最多一个用户定义的转化。

在这里,它将寻找:

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

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

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

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

然后调用Derived::operator(Derived const&)

行动

如果我们使用更多跟踪扩充代码,我们可以see it in action

#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())上使用此关键字。

如果我们在explicit之前使用Derived::Derived(Base const&),则代码变得格式不正确,应该被编译器拒绝。

答案 3 :(得分:2)

由于你已经为Derived定义了一个构造函数,它接受类型Base并且你正在向下转换Base,编译器会选择最适合upcast的构造函数,在本例中是Dervied(const Base&amp; b)you'已定义。如果您没有定义此构造函数,则在尝试进行赋值时实际上会出现编译错误。有关详细信息,请参阅Linuxtopia

中的以下内容

答案 4 :(得分:1)

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