我一直认为我对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&)
是赋值表达式。
那么为什么会这样呢?
答案 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
临时值。