Нарезка объектов, это преимущество? - PullRequest
5 голосов
/ 05 марта 2010

Объектная нарезка - это то, что объект теряет некоторые свои атрибуты или функции, когда дочерний класс назначается базовому классу. Нечто вроде

Class A{

}
Class B extends A{

}

Class SomeClass{
A a = new A();
B b = new B();

// Some where if might happen like this */
a = b; (Object slicing happens)

}

Говорим ли мы, что нарезка объектов полезна? Если да, то можете ли вы сказать мне, как нарезка объектов может быть полезной при разработке и где она может быть полезна?

1 Ответ

21 голосов
/ 05 марта 2010

В C ++ вы должны думать о срезе объекта как о преобразовании из производного типа в базовый тип [*]. Создан новый объект, «вдохновленный реальной историей».

Иногда это то, что вы хотели бы сделать, но результат ни в каком смысле не тот же объект, что и оригинал. Когда нарезка объектов идет не так, это когда люди не обращают внимания и думают, что это тот же объект или его копия.

Обычно это не выгодно. На самом деле это обычно происходит случайно, когда кто-то передает по значению, когда он намеревался передать по ссылке.

Довольно сложно придумать пример, когда нарезка является определенно правильной вещью, потому что довольно сложно (особенно в C ++) придумать пример, когда неабстрактный базовый класс является определенно правильной вещью. сделать. Это важный проектный момент, и его нельзя игнорировать - если вы обнаружите, что нарезаете объект, намеренно или случайно, вполне вероятно, что ваша иерархия объектов неверна с самого начала. Либо базовый класс не должен использоваться в качестве базового класса, либо он должен иметь хотя бы одну чисто виртуальную функцию и, следовательно, не может быть разрезанным или передаваемым по значению.

Итак, любой пример, который я привел, когда объект преобразуется в объект его базового класса, справедливо вызвал бы возражение: «Подождите минутку, что вы делаете, унаследовав от конкретного класса в первую очередь?». Если разрезание является случайным, то это, вероятно, ошибка, а если она преднамеренная, то это, вероятно, «запах кода».

Но ответом может быть "да, хорошо, этот не должен действительно быть таким, как все структурировано, но, учитывая, что они структурированы таким образом, мне нужно преобразовать из производный класс для базового класса, и это по определению является срез ". В этом духе, вот пример:

struct Soldier {
    string name;
    string rank;
    string serialNumber;
};

struct ActiveSoldier : Soldier {
    string currentUnit;
    ActiveSoldier *commandingOfficer; // the design errors multiply!
    int yearsService;
};

template <typename InputIterator>
void takePrisoners(InputIterator first, InputIterator last) {
    while (first != last) {
        Soldier s(*first);
        // do some stuff with name, rank and serialNumber
       ++first;
    }
}

Теперь требование к шаблону функции takePrisoners состоит в том, чтобы его параметр был итератором для типа, конвертируемого в Soldier. Это не обязательно должен быть производный класс, и мы не имеем прямого доступа к членам "name" и т. Д., Поэтому takePrisoners попытался предложить максимально простой интерфейс для реализации, учитывая, что ограничения (a) должны работать с Soldier и (b) должна быть возможность писать другие типы, с которыми он также работает.

ActiveSoldier - еще один такой тип. По причинам, наиболее известным только автору этого класса, он решил публично наследовать от солдата, а не предоставлять перегруженный оператор преобразования. Мы можем спорить, действительно ли это хорошая идея, но давайте предположим, что мы застряли с ней. Поскольку это производный класс, он может быть преобразован в Soldier. Это преобразование называется ломтик. Следовательно, если мы вызовем takePrisoners, передавая итераторы begin() и end() для вектора ActiveSoldiers, то мы нарежем их.

Возможно, вы могли бы придумать аналогичные примеры для OutputIterator, где получатель заботится только о части базового класса доставляемых объектов и поэтому позволяет их разрезать по мере их записи в итератор.

Причина, по которой он «пахнет кодом», заключается в том, что мы должны рассмотреть (а) переписывание ActiveSoldier и (б) изменение Soldier, чтобы к нему можно было обращаться с использованием функций вместо доступа к членам, чтобы мы могли абстрагировать этот набор функций как интерфейс, который другие типы могут реализовывать независимо, так что takePrisoners не нужно преобразовывать в Soldier. Любой из них устранит необходимость в срезе и будет иметь потенциальные преимущества для простоты расширения нашего кода в будущем.

[*] потому что он один. Последние две строки ниже делают то же самое:

struct A {
    int value;
    A(int v) : value(v) {}
};

struct B : A {
    int quantity;
    B(int v, int q) : A(v), quantity(q) {}
};

int main() {
    int i = 12;  // an integer
    B b(12, 3);  // an instance of B
    A a1 = b;    // (1) convert B to A, also known as "slicing"
    A a2 = i;    // (2) convert int to A, not known as "slicing"
}

Единственное отличие состоит в том, что (1) вызывает конструктор копирования A (который компилятор предоставляет, хотя код этого не делает), тогда как (2) вызывает конструктор int A.

Как сказал кто-то еще, Java не выполняет нарезку объектов. Если код, который вы предоставили, был превращен в Java, то никакого разделения на объекты не происходило бы. Переменные Java являются ссылками, а не объектами, поэтому постусловие a = b состоит лишь в том, что переменная «a» ссылается на тот же объект, что и переменная «b» - изменения по одной ссылке можно увидеть по другой ссылке, и так далее , Они просто ссылаются на это другим типом, который является частью полиморфизма. Типичная аналогия для этого заключается в том, что я могу думать о человеке как о «моем брате» [**], а кто-то другой может думать о том же человеке, что и о «моем викарии». Один и тот же объект, другой интерфейс.

Вы можете получить Java-подобный эффект в C ++, используя указатели или ссылки:

B b(24,7);
A *a3 = &b; // No slicing - a3 is a pointer to the object b
A &a4 = b;  // No slicing - a4 is a reference to (pseudonym for) the object b

[**] На самом деле, мой брат не викарий.

...