Что такое нарезка объектов?

Кто-то упомянул это в IRC как проблему нарезки.

Ответ 1

"Slicing" - это то, где вы назначаете объект производного класса экземпляру базового класса, тем самым теряя часть информации - часть из них "нарезана".

Например,

class A {
   int foo;
};

class B : public A {
   int bar;
};

Таким образом, объект типа B имеет два элемента данных: foo и bar.

Тогда, если вы должны были написать это:

B b;

A a = b;

Затем информация в B о члене bar теряется в a.

Ответ 2

Большинство ответов здесь не в состоянии объяснить, в чем проблема нарезки. Они объясняют только доброкачественные случаи нарезки, а не предательские. Предположим, как и другие ответы, что вы имеете дело с двумя классами A и B, где B происходит (публично) из A.

В этой ситуации C++ позволяет передать экземпляр B оператору присваивания A (а также конструктору копирования). Это работает, потому что экземпляр B может быть преобразован в const A&, и это то, что операторы присваивания и конструкторы копирования ожидают, что их аргументы будут.

Доброкачественный случай

B b;
A a = b;

Там ничего плохого не происходит - вы запросили экземпляр A, который является копией B, и именно это вы и получите. Конечно, a не будет содержать некоторых членов b, но как это сделать? В конце концов, это A, а не B, так что он даже не слышал об этих участниках, не говоря уже о том, чтобы хранить их.

Коварный случай

B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!

Вы можете подумать, что b2 будет копией b1 впоследствии. Но, увы, это не! Если вы осмотрите его, вы обнаружите, что b2 является Франкенштейновским существом, сделанным из некоторых кусков b1 (кусков, которые B наследует от A), и некоторых кусков b2 (кусков что только B содержит). Ой!

Что случилось? Ну, C++ по умолчанию не обрабатывает операторы присваивания как virtual. Таким образом, строка a_ref = b1 будет вызывать оператор присваивания A, а не оператора B. Это связано с тем, что для не виртуальных функций объявленный тип (который является A&) определяет, какая функция вызывается, в отличие от фактического типа (который будет B ], поскольку a_ref ссылается на экземпляр B). Теперь оператор присваивания A, очевидно, знает только о членах, объявленных в A, поэтому он будет копировать только те, которые оставят добавленные члены в B.

Решение

Назначение только частям объекта обычно не имеет смысла, но, к сожалению, C++ не предоставляет встроенного способа запретить это. Вы можете, однако, свернуть свое собственное. Первый шаг - сделать оператор присваивания виртуальным. Это гарантирует, что всегда вызывается фактический оператор присваивания типа, а не объявленный тип. Второй шаг - использовать dynamic_cast, чтобы убедиться, что назначенный объект имеет совместимый тип. Третий шаг - выполнить фактическое назначение (защищенного!) Члена assign(), поскольку B assign(), вероятно, захочет использовать A assign() для копирования членов A.

class A {
public:
  virtual A& operator= (const A& a) {
    assign(a);
    return *this;
  }

protected:
  void assign(const A& a) {
    // copy members of A from a to this
  }
};

class B : public A {
public:
  virtual B& operator= (const A& a) {
    if (const B* b = dynamic_cast<const B*>(&a))
      assign(*b);
    else
      throw bad_assignment();
    return *this;
  }

protected:
  void assign(const B& b) {
    A::assign(b); // Let A assign() copy members of A from b to this
    // copy members of B from b to this
  }
};

Обратите внимание, что для удобства B operator= ковариантно переопределяет тип возвращаемого значения, поскольку он знает, что он возвращает экземпляр B.

Ответ 3

Если у вас есть базовый класс A и производный класс B, вы можете сделать следующее.

void wantAnA(A myA)
{
   // work with myA
}

B derived;
// work with the object "derived"
wantAnA(derived);

Теперь для метода wantAnA требуется копия derived. Однако объект derived не может быть полностью скопирован, так как класс B может изобретать дополнительные переменные-члены, которые не находятся в его базовом классе A.

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

  • он может быть неполным,
  • он ведет себя как объект A -object (все особые поведения класса B теряются).

Ответ 4

Все это хорошие ответы. Я просто хотел бы добавить пример выполнения при передаче объектов по значению vs по ссылке:

#include <iostream>

using namespace std;

// Base class
class A {
public:
    A() {}
    A(const A& a) {
        cout << "'A' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am an 'A'" << endl; }
};

// Derived class
class B: public A {
public:
    B():A() {}
    B(const B& a):A(a) {
        cout << "'B' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am a 'B'" << endl; }
};

void g(const A & a) {
    a.run();
}

void h(const A a) {
    a.run();
}

int main() {
    cout << "Call by reference" << endl;
    g(B());
    cout << endl << "Call by copy" << endl;
    h(B());
}

Вывод:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'

Ответ 5

Третий матч в google для "С++ slicing" дает мне эту статью в Википедии http://en.wikipedia.org/wiki/Object_slicing и это (с подогревом, но первые несколько сообщений определяют проблема): http://bytes.com/forum/thread163565.html

Поэтому, когда вы назначаете объект подкласса суперклассу. Суперкласс ничего не знает о дополнительной информации в подклассе и не имеет места для его хранения, поэтому дополнительная информация получает "разрезанный".

Если эти ссылки не дают достаточно информации для "хорошего ответа", пожалуйста, отредактируйте свой вопрос, чтобы сообщить нам, что еще вы ищете.

Ответ 6

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

Рассмотрим класс A и класс B, полученный из A. Повреждение памяти может произойти, если часть A имеет указатель p и экземпляр B, который указывает p на B дополнительных данных. Затем, когда дополнительные данные удаляются, p указывает на мусор.

Ответ 7

В С++ объект производного класса может быть назначен объекту базового класса, но другой способ невозможен.

class Base { int x, y; };

class Derived : public Base { int z, w; };

int main() 
{
    Derived d;
    Base b = d; // Object Slicing,  z and w of d are sliced off
}

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

Ответ 8

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

Также подумал, что кто-то должен также упомянуть, что вам следует делать, чтобы избежать нарезки... Получите копию Стандартов кодирования С++, 101 правила и рекомендации. Работа с нарезкой - # 54.

Он предлагает несколько сложный шаблон для полного решения этой проблемы: иметь защищенный конструктор копии, защищенный чистый виртуальный DoClone и публичный Clone с утверждением, который скажет вам, если (далее) производный класс не смог реализовать DoClone правильно. (Метод Clone делает надлежащую глубокую копию полиморфного объекта.)

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

Ответ 9

Проблема разрезания на С++ возникает из семантики значений ее объектов, которая оставалась в основном из-за совместимости с C-структурами. Вам нужно использовать явный синтаксис ссылок или указателей для достижения "нормального" поведения объекта, который встречается на большинстве других языков, которые выполняют объекты, т.е. Объекты всегда передаются по ссылке.

Короткими ответами являются то, что вы срезаете объект, назначая производный объект базовому объекту по значению, т.е. оставшийся объект является лишь частью производного объекта. Чтобы сохранить семантику стоимости, нарезка является разумным поведением и имеет относительно редкое применение, которое не существует на большинстве других языков. Некоторые люди считают это особенностью С++, в то время как многие считают ее одной из особенностей/недостатков С++.

Ответ 10

1. ОПРЕДЕЛЕНИЕ ПРОБЛЕМЫ СКОРОСТИ

Если D является производным классом базового класса B, то вы можете назначить объект типа Derived переменной (или параметру) типа Base.

Пример

class Pet
{
 public:
    string name;
};
class Dog : public Pet
{
public:
    string breed;
};

int main()
{   
    Dog dog;
    Pet pet;

    dog.name = "Tommy";
    dog.breed = "Kangal Dog";
    pet = dog;
    cout << pet.breed; //ERROR

Несмотря на то, что указанное присвоение разрешено, значение, присвоенное переменной pet, теряет поле своей породы. Это называется проблемой slicing.

2. КАК УСТАНОВИТЬ ПРОБЛЕМУ СКОРОСТИ

Чтобы устранить проблему, мы используем указатели на динамические переменные.

Пример

Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;         
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed; 

В этом случае ни один из элементов данных или функций-членов динамической переменной на которые указывает ptrD (объект класса потомка), будет потерян. Кроме того, если вам нужно использовать функции, функция должна быть виртуальной.

Ответ 11

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

Если я передаю объект подкласса в качестве параметра метода, который принимает параметр суперкласса типа, я должен обязательно знать об этом и знать внутренне, вызываемый метод будет работать с суперклассом (aka baseclass) только объект.

Мне кажется, что только необоснованное ожидание того, что предоставление подкласса, где запрашивается базовый класс, каким-то образом приведет к конкретным результатам подкласса, вызовет проблему разрезания. Его либо плохой дизайн при использовании метода, либо недостаточная реализация подкласса. Я предполагаю, что это обычно результат жертвовать хорошим дизайном ООП в пользу целесообразности или повышения производительности.

Ответ 12

Хорошо, я попробую после чтения многих сообщений, объясняющих разбиение объектов, но не так, как это становится проблематичным.

Порочный сценарий, который может привести к повреждению памяти, следующий:

  • Класс предоставляет (случайно, возможно, сгенерированное компилятором) назначение в полиморфном базовом классе.
  • Клиент копирует и нарезает экземпляр производного класса.
  • Клиент вызывает функцию виртуального члена, которая обращается к состоянию среза.

Ответ 13

Разделение означает, что данные, добавленные подклассом, отбрасываются, когда объект подкласса передается или возвращается по значению или из функции, ожидающей объект базового класса.

Объяснение: Рассмотрим следующее объявление класса:

           class baseclass
          {
                 ...
                 baseclass & operator =(const baseclass&);
                 baseclass(const baseclass&);
          }
          void function( )
          {
                baseclass obj1=m;
                obj1=m;
          }

Поскольку функции копирования базового класса ничего не знают о производном, копируется только базовая часть производного. Это обычно называют нарезкой.

Ответ 14

class A 
{ 
    int x; 
};  

class B 
{ 
    B( ) : x(1), c('a') { } 
    int x; 
    char c; 
};  

int main( ) 
{ 
    A a; 
    B b; 
    a = b;     // b.c == 'a' is "sliced" off
    return 0; 
}

Ответ 15

когда объект производного класса присваивается объекту базового класса, дополнительные атрибуты объекта производного класса вырезаются (отбрасываются) из объекта базового класса.

class Base { 
int x;
 };

class Derived : public Base { 
 int z; 
 };

 int main() 
{
Derived d;
Base b = d; // Object Slicing,  z of d is sliced off
}

Ответ 16

Когда классу класса Derived присваивается объект Base class Object, все члены объекта производного класса копируются в объект базового класса, за исключением членов, которых нет в базовом классе. Эти члены удаляются компилятором. Это называется разделением объектов.

Вот пример:

#include<bits/stdc++.h>
using namespace std;
class Base
{
    public:
        int a;
        int b;
        int c;
        Base()
        {
            a=10;
            b=20;
            c=30;
        }
};
class Derived : public Base
{
    public:
        int d;
        int e;
        Derived()
        {
            d=40;
            e=50;
        }
};
int main()
{
    Derived d;
    cout<<d.a<<"\n";
    cout<<d.b<<"\n";
    cout<<d.c<<"\n";
    cout<<d.d<<"\n";
    cout<<d.e<<"\n";


    Base b = d;
    cout<<b.a<<"\n";
    cout<<b.b<<"\n";
    cout<<b.c<<"\n";
    cout<<b.d<<"\n";
    cout<<b.e<<"\n";
    return 0;
}

Он будет генерировать:

[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'

Ответ 17

Я просто столкнулся с проблемой нарезки и быстро приземлился здесь. Поэтому позвольте мне добавить мои два цента к этому.

Возьмем пример из "производственного кода" (или что-то вроде близкого):


Скажем, у нас есть что-то, что рассылает действия. Например, пользовательский интерфейс центра управления. Этот пользовательский интерфейс должен получить список вещей, которые в настоящее время могут быть отправлены. Поэтому мы определяем класс, содержащий информацию о доставке. Позвольте называть его Action. Таким образом, Action имеет некоторые переменные-члены. Для простоты имеем просто 2, являющееся a std::string name и a std::function<void()> f. Затем он имеет void activate(), который просто выполняет элемент f.

Таким образом, пользовательский интерфейс получает std::vector<Action>. Представьте себе некоторые функции, такие как:

void push_back(Action toAdd);

Теперь мы выяснили, как это выглядит с точки зрения пользовательского интерфейса. Пока нет проблем. Но какой-то другой парень, который работает над этим проектом, внезапно решает, что существуют специальные действия, требующие дополнительной информации в объекте Action. По какой причине. Это также можно решить с помощью лямбда-захватов. Этот пример не принимается 1-1 из кода.

Итак, парень получает от Action, чтобы добавить свой собственный вкус.
Он передает экземпляр своего домашнего класса в push_back, но затем программа переходит в haywire.

И что случилось?
Как вы могли догадаться: объект был нарезан.

Дополнительная информация из экземпляра потеряна, а f теперь подвержена действию undefined.


Надеюсь, что этот пример освещает те люди, которые не могут себе представить, когда говорят о A и B в некотором роде.