构造函数混乱

发布于 2024-11-10 16:50:04 字数 796 浏览 0 评论 0原文

我一直认为我对 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&运算符 = (const Base&) 不存在。

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

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

I always think I know C++ pretty well, but sometimes I'm surprised by even the most fundamental things.

In the following scenario, I'm confused as to why the constructor Derived::Derived(const Base&) is invoked:

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. Now, I thought I knew C++ pretty well, but I can't figure out why that constructor would be invoked. 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.

Instead, it called a constructor, even though the expression d = b is an assignment expression.

So why does this happen?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

感情废物 2024-11-17 16:50:04

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

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

d = b can happen because b is converted to Derived.
The second constructor is used for automatic type conversion.
It's like d = (Derived) b

Derived isa Base, but Base isn'ta Derived, so it has to be converted before assignment.

一个人练习一个人 2024-11-17 16:50:04

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

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

就我个人而言,我认为你弄乱了你的代码示例,因为,通常将一等基分配给派生在没有转换的情况下是没有意义的

assigning base to derived? perhaps you meant (a) by ref (b) or derived to base. 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).

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

逆流 2024-11-17 16:50:04

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

  • 赋值运算符永远不会被继承
  • 不显式的构造函数,或者转换运算符(运算符 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
}

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

< 每当存在“阻抗”不匹配

时,例如这里:

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

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

在这里,它将查找:

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

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

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

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

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

实际操作

如果我们用更多的跟踪来扩充代码,我们就可以看到它的实际操作

#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 中,也可以在转换运算符(运算符 T())上使用此关键字。

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

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

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:

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
  • a Base& is provided

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)
  • a conversion operator in Base that would yield a Derived item

There is no Base::operator Derived() but there is a Derived::Derived(Base const&) constructor.

Therefore our conversion sequence is defined for us:

  • Base&
  • Base const& (trivial)
  • Derived (using Derived::Derived(Base const&))
  • Derived const& (temporary object bound to a const reference)

And then Derived::operator(Derived const&) is called.

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. To do so, one need to prefix the declaration of the constructor using the explicit keyword.

In C++0x, it becomes possible to use this keyword on conversion operators (operator T()) as well.

If here we use explicit before Derived::Derived(Base const&) then the code becomes ill-formed and should be rejected by the compiler.

掩饰不了的爱 2024-11-17 16:50:04

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

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. 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.

莫相离 2024-11-17 16:50:04

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

It can't assign value of different type, so it should first construct a Derived temporary.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文