构造函数混乱
我一直认为我对 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
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.
将基数分配给派生?也许你的意思是(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
这里有两个相互作用的功能:
运算符 T()
)定义可以隐式用作一部分的用户转换转换序列的赋值运算符永远不会被继承
一个简单的代码示例:
来自ideone:
< 每当存在“阻抗”不匹配
时,例如这里:
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&)
。实际操作
如果我们用更多的跟踪来扩充代码,我们就可以看到它的实际操作 。
哪些输出:
注意:防止这种情况?
在 C++ 中,可以删除用于转换序列的构造函数。为此,需要使用
explicit
关键字在构造函数的声明前面添加前缀。在 C++0x 中,也可以在转换运算符(
运算符 T()
)上使用此关键字。如果我们在
Derived::Derived(Base const&)
之前使用explicit
,那么代码就会变得格式错误,应该被编译器拒绝。There are two interacting features at play here:
operator T()
) define a user-conversion that can be used implicitly as part of a conversion sequenceAssignement Operators are never inherited
A simple code example:
From ideone:
Conversion sequence
Whenever there is an "impedance" mismatch, such as here:
Derived::operator=
expects aDerived const&
argumentBase&
is providedthe 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:
Derived
that can be invoked with aBase&
(not explicit)Base
that would yield aDerived
itemThere is no
Base::operator Derived()
but there is aDerived::Derived(Base const&)
constructor.Therefore our conversion sequence is defined for us:
Base&
Base const&
(trivial)Derived
(usingDerived::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.
Which outputs:
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
beforeDerived::Derived(Base const&)
then the code becomes ill-formed and should be rejected by the compiler.由于您已经为 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.
它不能分配不同类型的值,因此它应该首先构造一个
Derived
临时值。It can't assign value of different type, so it should first construct a
Derived
temporary.