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

Ответы:

610

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

Например,

class A {
   int foo;
};

class B : public A {
   int bar;
};

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

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

B b;

A a = b;

Тогда информация bо члене barтеряется в a.

Дэвид Диббен
источник
66
Очень информативно, но см. Stackoverflow.com/questions/274626#274636 для примера того, как нарезка происходит во время вызовов метода (что подчеркивает опасность немного лучше, чем простой пример назначения).
Блэр Конрад
55
Интересно. Я программирую на C ++ в течение 15 лет, и эта проблема мне никогда не приходила в голову, поскольку я всегда передавал объекты по ссылке в целях эффективности и личного стиля. Идет, чтобы показать, как хорошие привычки могут помочь вам.
Карл Билефельдт
10
@Felix Спасибо, но я не думаю, что приведение обратно (поскольку не арифметика указателей) будет работать, A a = b; aтеперь это объект типа, у Aкоторого есть копия B::foo. Думаю, будет заброшено возвращать его обратно.
37
Это не «нарезка», или, по крайней мере, доброкачественный вариант. Настоящая проблема возникает, если вы делаете B b1; B b2; A& b2_ref = b2; b2 = b1. Вы можете подумать , как вы скопировали b1на b2, но у вас нет! Вы скопировали часть из b1к b2(часть , b1которая Bнаследуется от A), и оставили другие части b2неизменными. b2теперь это франкенштейновское существо, состоящее из нескольких b1кусков, за которыми следуют несколько кусков b2. Тьфу! Даунтинг, потому что я думаю, что ответ очень обманчив.
FGP
24
@fgp Ваш комментарий должен гласить B b1; B b2; A& b2_ref = b2; b2_ref = b1" Настоящая проблема возникает, если вы " ... происходят от класса с не виртуальным оператором присваивания. Есть Aдаже предназначен для вывода? У него нет виртуальных функций. Если вы наследуете тип, вам приходится иметь дело с тем фактом, что его функции-члены могут быть вызваны!
любопытный парень
510

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

В этой ситуации, C ++ позволяет передавать экземпляр Bдля Aоператора присваивания «s (а также конструктор копирования). Это работает, потому что экземпляр Bможет быть преобразован в a 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«s assign(), вероятно , захотите использовать A» S , 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's assign() copy members of A from b to this
    // copy members of B from b to this
  }
};

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

ГСВ
источник
12
ИМХО, проблема состоит в том, что существует два различных вида заменяемости, которые могут подразумеваться наследованием: либо любое derivedзначение может быть дано коду, ожидающему baseзначение, либо любая производная ссылка может использоваться в качестве базовой ссылки. Я хотел бы видеть язык с системой типов, которая рассматривает обе концепции отдельно. Во многих случаях производная ссылка должна заменять базовую ссылку, но производные экземпляры не должны заменять базовые; Есть также много случаев, когда экземпляры должны быть конвертируемыми, но ссылки не должны заменять.
суперкат
16
Я не понимаю, что же такого плохого в вашем "коварном" деле. Вы заявили, что хотите: 1) получить ссылку на объект класса A и 2) привести объект b1 к классу A и скопировать его содержимое в ссылку на класс A. Что здесь на самом деле неправильно, так это правильная логика, лежащая в основе данный код. Другими словами, вы взяли небольшую рамку изображения (A), поместили ее на более крупное изображение (B) и закрасили эту рамку, жалуясь позже, что ваше увеличенное изображение теперь выглядит некрасиво :) Но если мы просто рассмотрим эту рамку, это выглядит довольно хорошо, как и хотел художник, верно? :)
Младен Б.
12
Проблема заключается в том, что C ++ по умолчанию предполагает очень сильный тип заменимости - он требует, чтобы операции базового класса работали правильно на экземплярах подкласса. И это даже для операций, которые компилятор автоматически генерирует как присваивание. Так что недостаточно не испортить свои собственные операции в этом отношении, вы также должны явно отключить неправильные операции, сгенерированные компилятором. Или, конечно, держаться подальше от публичного наследства, которое обычно всегда является хорошим предложением ;-)
fgp
14
Другой распространенный подход - просто отключить оператор копирования и присваивания. Для классов в иерархии наследования обычно нет причин использовать значение вместо ссылки или указателя.
Сиюань Рен
13
Что за? Я понятия не имел, что операторы могут быть отмечены как виртуальные
Пол
155

Если у вас есть базовый класс 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-объект (все особое поведение класса Bпотеряно).
черный
источник
41
C ++ - это не Java! Если wantAnA(как следует из названия!) Хочет A, то это то, что он получает. И пример A, будет вести себя как A. Как это удивительно?
FGP
83
@fgp: Это удивительно, потому что вы не передаете A в функцию.
Черный
10
@fgp: поведение похоже. Тем не менее, для среднего программиста C ++ это может быть менее очевидно. Насколько я понял вопрос, никто не "жалуется". Это как раз то, как компилятор справляется с ситуацией. Имхо, лучше вообще не разрезать, передавая (const) ссылки.
Чёрный
9
@ThomasW Нет, я бы не выбрасывал наследование, а использовал ссылки. Если подпись wantAnA была бы недействительной wantAnA (const A & myA) , то среза не было. Вместо этого передается доступная только для чтения ссылка на объект вызывающей стороны.
Черный,
14
проблема главным образом в автоматическом приведении, от которого компилятор выполняет derivedтип A. Неявное приведение всегда является источником неожиданного поведения в C ++, потому что зачастую трудно понять, просматривая код локально, что произошло приведение.
pqnet
41

Это все хорошие ответы. Я просто хотел бы добавить пример выполнения при передаче объектов по значению 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'
GEH
источник
Привет. Отличный ответ, но у меня есть один вопрос. Если я сделаю что-то вроде этого ** dev d; base * b = & d; ** Нарезка также имеет место?
Адриан
@Adrian Если вы введете некоторые новые функции-члены или переменные-члены в производный класс, они не будут доступны непосредственно из указателя базового класса. Однако вы все равно можете получить к ним доступ изнутри перегруженных виртуальных функций базового класса. Смотрите это: godbolt.org/z/LABx33
Вишал Шарма
30

Третье совпадение в Google для «нарезки на C ++» дает мне статью в Википедии http://en.wikipedia.org/wiki/Object_slicing и вот это (горячо, но первые несколько постов определяют проблему): http://bytes.com/ форум / thread163565.html

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

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

Архетипический Павел
источник
29

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

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

Уолтер Брайт
источник
3
Пожалуйста, объясните, как может происходить повреждение памяти.
Foraidt
4
Я забыл, что копия ctor будет сбрасывать vptr, моя ошибка. Но вы все равно можете получить искажение, если у A есть указатель, а B устанавливает его так, чтобы он указывал на секцию B, которая вырезана.
Уолтер Брайт
18
Эта проблема не ограничивается только нарезкой. Любые классы, которые содержат указатели, будут иметь сомнительное поведение с оператором присваивания по умолчанию и конструктором копирования.
Weeble
2
@Weeble - именно поэтому в этих случаях вы переопределяете деструктор по умолчанию, оператор присваивания и конструктор копирования.
Бьярке Фрейнд-Хансен
7
@Weeble: Что делает нарезку объектов хуже, чем обычные исправления указателей, так это то, что, чтобы быть уверенным, что вы не допустили нарезку, базовый класс должен предоставлять конструкторы преобразования для каждого производного класса . (Почему? Любые пропущенные производные классы восприимчивы к тому, чтобы быть подобранными ctor-копией базового класса, поскольку они Derivedнеявно конвертируются в Base.) Это явно противоречит принципу Open-Closed и является большой нагрузкой на обслуживание.
j_random_hacker
11

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

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

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

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

ididak
источник
5
" " нормальное "поведение объекта ", это не "нормальное поведение объекта", это ссылка на семантику . И это никак не связано с Си struct, совместимостью или другим бессмысленным смыслом, который вам сказал любой случайный ООП-жрец.
любопытный парень
4
@curiousguy Аминь, брат. Грустно видеть, как часто C ++ теряется из-за того, что не является Java, когда семантика значений является одной из вещей, которые делают C ++ настолько безумно мощным.
FGP
Это не особенность, не изворотливость / несоответствие. Это нормальное поведение при копировании в стеке, так как вызов функции с аргументом типа arg или (той же) переменной размещения стека Baseдолжен занимать ровно sizeof(Base)байты в памяти с возможным выравниванием, может быть, поэтому «назначение» (on-stack-copy) ) не будет копировать производные члены класса, их смещения находятся за пределами sizeof. Для того, чтобы избежать «потери данных», просто указатель использования, как кто - либо другой, так как указатель памяти фиксируется на месте и размера, в то время как стек очень volitile
Кролл
Определенно неудача C ++. Присвоение производного объекта базовому объекту должно быть запрещено, тогда как привязка производного объекта к ссылке или указателю базового класса должна быть в порядке.
Джон З. Ли
7

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

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

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

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

Стив Штайнер
источник
3
« Вы также можете пометить конструктор копирования на явном основании », что совсем не помогает.
любопытный парень
6

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, теряет свое поле породы. Это называется проблемой нарезки .

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

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

ПРИМЕР

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

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

haberdar
источник
7
Я понимаю часть «нарезки», но не понимаю «проблемы». Какова проблема, что некоторое состояние dog, которое не является частью класса Pet( breedчлен данных), не копируется в переменную pet? Код интересует только Petданные членов - по-видимому. Нарезка - определенно «проблема», если она нежелательна, но я не вижу этого здесь.
любопытный парень
4
« ((Dog *)ptrP)Я предлагаю использоватьstatic_cast<Dog*>(ptrP)
curiousguy
Я предлагаю указать, что вы заставите строку «размножаться» со временем утечку памяти без виртуального деструктора (деструктор «строка» не будет вызываться) при удалении через «ptrP» ... Почему то, что вы показываете, проблематично? Исправление - в основном правильный дизайн класса. Проблема в этом случае заключается в том, что записывать конструкторы для управления видимостью при наследовании утомительно и легко забыто. Вы не приблизитесь к опасной зоне с вашим кодом, так как в нем не задействован или даже не упоминается полиморфизм (разрезание обрежет ваш объект, но не приведет к аварийному завершению вашей программы).
Чувак
24
-1 Это совершенно не объясняет реальной проблемы. C ++ имеет семантику значений, а не эталонную семантику, такую ​​как Java, так что этого вполне следует ожидать. И «исправление» действительно является примером действительно ужасного кода C ++. «Исправление» несуществующих проблем, таких как этот тип нарезки, путем использования динамического выделения - это рецепт с ошибочным кодом, утечкой памяти и ужасной производительностью. Обратите внимание , что есть случаи , когда нарезка это плохо, но этот ответ failes , чтобы указать на них. Подсказка: проблема начинается, если вы назначаете через ссылки .
FGP
Вы даже понимаете, что попытка получить доступ к члену типа, который не определен ( Dog::breed), никоим образом не является ОШИБКОЙ, связанной с SLICING?
Croll
4

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

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

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

Minok
источник
3
Но помните, Минок, что вы НЕ передаете ссылку на этот объект. Вы передаете НОВУЮ копию этого объекта, но используете базовый класс для копирования в процессе.
Арафангион
защищенное копирование / назначение на базовый класс и эта проблема решена.
Чувак
1
Вы правы. Хорошей практикой является использование абстрактных базовых классов или ограничение доступа к копированию / назначению. Тем не менее, это не так легко обнаружить, когда он там, и легко забыть о нем позаботиться. Вызов виртуальных методов с использованием sliced ​​* может вызвать загадочные вещи, если вы уйдете без нарушения прав доступа.
Чувак
1
Из моих курсов по программированию на С ++ в университете я вспоминаю, что для каждого создаваемого нами класса существовали передовые практики, которые требовали написания конструкторов по умолчанию, конструкторов копирования и операторов присваивания, а также деструктора. Таким образом, вы убедились, что создание копии и тому подобное происходило именно так, как вам нужно, при написании класса ... а не позже, когда появлялось какое-то странное поведение.
Минок
3

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

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

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

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

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

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

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

Сантош
источник
1
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; 
}
quidkid
источник
4
Не могли бы вы дать некоторые дополнительные детали? Чем ваш ответ отличается от уже опубликованных?
Алексис Пиджен
2
Я думаю, что больше объяснений не будет плохим.
петух
-1

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

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
}
Варун Кумар
источник
-1

Когда производный класс Object назначается базовому классу 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'
Гулам Мойнул Квадир
источник
Проголосовал, потому что это не хороший пример. Это также не сработает, если вместо копирования d в b вы будете использовать указатель, в этом случае d и e все еще будут существовать, но у Base нет этих членов. Ваш пример показывает только то, что вы не можете получить доступ к членам, которых нет в классе.
Стефан Фабиан,
-2

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

Давайте возьмем пример из «производственного кода» (или что-то вроде этого):


Допустим, у нас есть что-то, что отправляет действия. Пользовательский интерфейс центра управления, например.
Этот пользовательский интерфейс должен получить список вещей, которые в настоящее время могут быть отправлены. Таким образом, мы определяем класс, который содержит диспетчерскую информацию. Давайте назовем это Action. Таким образом, an 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но затем программа выходит из строя.

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

Дополнительная информация из экземпляра была потеряна и fтеперь подвержена неопределенному поведению.


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

Мартин Б.
источник