作者提到的不良结果是什么?

4
这个例子摘自Bruce Eckel的《C++编程思想》第14章节的"向上转型和复制构造函数"。
#include <iostream>
using namespace std;

class Parent
{
    int i;

    public:
    Parent(int ii) : i(ii) { cout << "Parent(int ii)\n"; }
    Parent(const Parent& b) : i(b.i) {  cout << "Parent(const Parent&)\n"; }
    Parent() : i(0) { cout << "Parent()\n"; }
    friend ostream& operator<<(ostream& os, const Parent& b)
            { return os << "Parent: " << b.i << endl; }
};

class Member
{
    int i;

    public:
    Member(int ii) : i(ii) { cout << "Member(int ii)\n"; }
    Member(const Member& m) : i(m.i) { cout << "Member(const Member&)\n"; }
    friend ostream& operator<<(ostream& os, const Member& m)
            { return os << "Member: " << m.i << endl; }
};

class Child : public Parent
{
    int i;
    Member m;

    public:
    Child(int ii) : Parent(ii), i(ii), m(ii) { cout << "Child(int ii)\n"; }
    friend ostream& operator<<(ostream& os, const Child& c)
            { return os << (Parent&)c << c.m << "Child: " << c.i << endl; }
};

int main() {
  Child c(2);
  cout << "calling copy-constructor: " << endl;
  Child c2 = c;
  cout << "values in c2:\n" << c2;
}

关于这段代码,作者发表了以下评论:

“Child 类的 << 操作符非常有趣,因为它在其中调用了 Parent 部分的 << 操作符:通过将 Child 对象强制转换为 Parent&(如果你将其强制转换为基类对象而不是引用,通常会得到不良结果):

return os << (Parent&)c << c.m << "Child: " << c.i << endl;

我也运行这个程序,通过用以下指令替换上述指令:
return os << (Parent)c << c.m << "Child: " << c.i << endl;

程序可以顺利运行,只有一个预期的区别。现在再次调用 Parent 的拷贝构造函数将参数 c 复制到 Parent::operator<<() 中。

那么作者所说的不良结果是什么呢?


1
https://dev59.com/YHVC5IYBdhLWcg3wfxQ8 - ephemient
1
@ephemient 我在这里看不出切片有什么问题。 - Belloc
2
并不是说这里有问题,但在一般情况下可能会有问题。 - ephemient
还有一个问题是从const转换为非const对象的强制转换。应该将(Parent&)c改为(const Parent &)c吗? - Ed Heal
@EdHeal:是的。更普遍地说,这里不需要进行强制类型转换。在“return”表达式之前简单地使用“Parent& p = c;”语句就足够了。 - Matthieu M.
@EdHeal 我也没有看到问题,因为Patent::operator<<()的第二个参数是一个const Parent& - Belloc
2个回答

2
问题在于,当你将一个是 Child(子类)的对象强制转换成 Parent(父类)(而不是 Parent&),你将会简单地切掉使 Child 成为 Child 的一切内容。
通常情况下,当你的类拥有虚函数(以及在类层次结构中通常都会有)时,你可以并且会(取决于内部布局、继承类的数量等等)修改 vptr,然后直接进入未定义行为的领域。也就是说,在类层次结构中不使用引用(或指针)有效地消除了所有魔术继承机制(也称为多态性)。
这有点像说 dog = plane; - 并通过使用reinterpret_cast(这正是 C 风格强制类型转换的本质)来剥夺编译器向你发出警告的任何机会,因为你告诉它闭嘴。

但在这种情况下没有虚函数。 - Belloc
当然,你可能会问,为什么需要层次结构?确切地说,这是演示代码。如果你在生产代码中习惯于“只要没有虚函数就可以重新解释转换”,那么你不会是第一个因为维护或重构需要添加虚方法但你的转换被忽略而导致代码严重崩溃并引起大量头痛的人。除此之外,你问作者的意思,他就是这个意思。 - cli_hlt
你也许是对的,但是虚拟继承的章节在他的书中稍后出现。 - Belloc
这并不妨碍认真的作者在适当的时候指出应该做和尤其是不应该做的事情。而这正是一个适当的例子,因为任何学习C++的人都会问:“为什么我不能简单地转换为父类?”。作者说:“不要这样做,因为这是错误的。”。他唯一忘记的事情就是写下“详细信息稍后跟进”。 - cli_hlt

1

有点离题...

经验法则:基类不应该可复制,而应该是可克隆的。

实现方法:要么禁用复制构造函数和复制赋值运算符,要么(简单地)创建一个纯虚方法。

放宽限制:如果没有纯虚方法,更容易将基类的复制构造函数和赋值运算符设为protected警告:这意味着子类现在有能力调用其父类的副本,可能会触发切片问题。

注意:对于C++11,这也适用于移动对应项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接