О каких нежелательных результатах говорит автор? - PullRequest
4 голосов
/ 10 февраля 2012

Этот пример был взят из главы 14 «Размышления в C ++» Брюса Экеля, «Обновление и конструктор копирования».

#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;
}

Автор делает следующий комментарий относительно этого кода:

"Оператор << для ребенка интересен тем, как он вызывает оператора << для родительской части внутри него: путем приведения Дочерний объект для Родителя & (если вы вместо этого приводите объект базового класса ссылки вы, как правило, получите нежелательные результаты): </p>

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

Я также запускаю программу, заменив вышеуказанную инструкцию на:

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

и пропрам работает без проблем, с одной ожидаемой разницей. Теперь конструктор копирования Parent вызывается снова для копирования аргумента c в Parent::operator<<().

Каковы же нежелательные результаты, о которых говорит автор?

Ответы [ 2 ]

2 голосов
/ 10 февраля 2012

Проблема в том, что, когда вы бросаете ребенка с трудом родителю (а не родителю), вы просто отрежете все, что делает ребенка ребенком.

Обычно, когда ваши классы имеют виртуальные функции (и обычно имеют внутри иерархии классов), вы можете и будете (в зависимости от внутренней компоновки, количества унаследованных классов и т. Д.) Модифицировать vptr, а затем вы пройдете весь путь вниз к царство неопределенного поведения. То есть неиспользование ссылок (или указателей) в иерархиях классов эффективно убивает все магические механизмы наследования (также называемые полиморфизмом).

Это немного похоже на высказывание собака = самолет; - и используя переинтерпретацию приведения (а именно это и есть приведение в стиле C), вы получаете любую возможность от компилятора предупредить вас об этом, потому что вы говорите ему заткнуться.

1 голос
/ 10 февраля 2012

Немного касательной ...

Полезное правило : базовый класс не должен быть копируемым, вместо этого он должен быть клонируемым.

Реализация : либо отключить конструктор копирования и оператор назначения копирования, либо (просто) создать чисто виртуальный метод.

ослабление : при отсутствии чисто виртуального метода это прощесделать конструктор копирования базового класса и оператор присваивания protected. Предупреждение : это означает, что дочерний класс теперь имеет возможность вызывать копию своего родителя, что может вызвать проблемы с нарезкой.

Примечание : с C ++11, это также относится к аналогам move .

...