简体   繁体   English

构造函数混淆

[英]Constructor confusion

I always think I know C++ pretty well, but sometimes I'm surprised by even the most fundamental things.我一直认为我非常了解 C++,但有时即使是最基本的事情我也会感到惊讶。

In the following scenario, I'm confused as to why the constructor Derived::Derived(const Base&) is invoked:在以下场景中,我对为什么调用构造函数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;
}

This outputs: Called Derived::Derived(const Base& b) , indicating that the second constructor in Derived was invoked.这输出: Called Derived::Derived(const Base& b) ,表示调用了Derived中的第二个构造函数。 Now, I thought I knew C++ pretty well, but I can't figure out why that constructor would be invoked.现在,我以为我非常了解 C++,但我不知道为什么会调用该构造函数。 I understand the whole "rule of four" concept, and I would think that the expression d = b would do one of two things: Either it would 1) invoke the implicit (compiler-generated) assignment operator of Base , or 2) Trigger a compiler error complaining that the function Derived& operator = (const Base&) does not exist.我理解整个“四规则”概念,我认为表达式d = b会做两件事之一:要么它会 1)调用Base的隐式(编译器生成)赋值运算符,要么 2)触发器一个编译器错误,抱怨 function Derived& operator = (const Base&)不存在。

Instead, it called a constructor , even though the expression d = b is an assignment expression.相反,它调用了一个构造函数,即使表达式d = b是一个赋值表达式。

So why does this happen?那么为什么会这样呢?

d = b can happen because b is converted to Derived. d = b 可能发生,因为 b 已转换为 Derived。 The second constructor is used for automatic type conversion.第二个构造函数用于自动类型转换。 It's like d = (Derived) b这就像 d = (Derived) b

Derived isa Base, but Base isn'ta Derived, so it has to be converted before assignment. Derived 是 Base,但 Base 不是 Derived,因此必须在赋值之前对其进行转换。

assigning base to derived?将基数分配给派生? perhaps you meant (a) by ref (b) or derived to base.也许您的意思是(a)由参考(b)或派生到基础。 This doesn't really make sense, but the compiler is correctly using your (non-explicit) constructor to convert the Base instance to a new Derived instance (which is subsequently assigned into d).这实际上没有意义,但是编译器正确地使用您的(非显式)构造函数将 Base 实例转换为新的 Derived 实例(随后将其分配给 d)。

Use an explicut constructor to prevent this from happening automatically.使用显式构造函数来防止这种情况自动发生。

Personally I think you messed up your code sample, because, normally assigning firstclass base to derived makes no sense without a conversion就我个人而言,我认为你搞砸了你的代码示例,因为通常将一等基分配给派生没有转换就没有意义

There are two interacting features at play here:这里有两个交互功能:

  • Assignment Operators are never inherited赋值运算符永远不会被继承
  • A constructor that is not explicit, or a conversion operator ( operator T() ) define a user-conversion that can be used implicitly as part of a conversion sequence非显式构造函数或转换运算符 ( operator T() ) 定义可隐式用作转换序列一部分的用户转换

Assignement Operators are never inherited赋值运算符永远不会被继承

A simple code example:一个简单的代码示例:

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

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

From ideone :来自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&)

Conversion sequence转换顺序

Whenever there is an "impedance" mismatch, such as here:每当出现“阻抗”不匹配时,例如这里:

  • Derived::operator= expects a Derived const& argument Derived::operator=需要一个Derived const&参数
  • a Base& is provided提供一个Base&

the compiler will try to establish a conversion sequence to bridge the gap.编译器将尝试建立一个转换序列来弥合差距。 Such a conversion sequence may contain at most one user-defined conversion.这样的转换序列最多可以包含一个用户定义的转换。

Here, it will look for:在这里,它将寻找:

  • any constructor of Derived that can be invoked with a Base& (not explicit)可以使用Base&调用的任何Derived构造函数(非显式)
  • a conversion operator in Base that would yield a Derived item Base中的转换运算符,将产生Derived

There is no Base::operator Derived() but there is a Derived::Derived(Base const&) constructor.没有Base::operator Derived()但有一个Derived::Derived(Base const&)构造函数。

Therefore our conversion sequence is defined for us:因此我们的转换顺序是为我们定义的:

  • Base&
  • Base const& (trivial) Base const& (微不足道的)
  • Derived (using Derived::Derived(Base const&) ) Derived (使用Derived::Derived(Base const&)
  • Derived const& (temporary object bound to a const reference) Derived const& (临时 object 绑定到 const 引用)

And then Derived::operator(Derived const&) is called.然后调用Derived::operator(Derived const&)

In action在行动

If we augment the code with some more traces, we can 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;
}

Which outputs:哪个输出:

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

Note: Preventing this?注意:防止这种情况?

It is possible, in C++, to remove a constructor for being used in conversion sequences.在 C++ 中,可以删除用于转换序列的构造函数。 To do so, one need to prefix the declaration of the constructor using the explicit keyword.为此,需要使用explicit关键字为构造函数的声明添加前缀。

In C++0x, it becomes possible to use this keyword on conversion operators ( operator T() ) as well.在 C++0x 中,也可以在转换运算符( operator T() )上使用这个关键字。

If here we use explicit before Derived::Derived(Base const&) then the code becomes ill-formed and should be rejected by the compiler.如果在这里我们在Derived::Derived(Base const&)之前使用explicit ,那么代码将变得格式错误并且应该被编译器拒绝。

Since you've defined a constructor for Derived which takes type Base and you are down-casting Base, the compiler chooses the most suitable constructor for the upcast, which in this case is the Dervied(const Base& b) you've defined.由于您已经为 Derived 定义了一个采用 Base 类型的构造函数并且您正在向下转换 Base,因此编译器会为向上转换选择最合适的构造函数,在本例中是您定义的 Derived(const Base& b)。 If you did not define this constructor you would actually get a compiling error when trying to make the assignment.如果您没有定义此构造函数,则在尝试进行分配时实际上会出现编译错误。 For more info, you can read the following at Linuxtopia .有关更多信息,您可以在Linuxtopia阅读以下内容。

It can't assign value of different type, so it should first construct a Derived temporary.它不能分配不同类型的值,所以它应该首先构造一个Derived临时。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM