Що таке нарізка предметів?


Відповіді:


608

"Нарізка" - це те, коли ви присвоюєте об'єкт похідного класу екземпляру базового класу, тим самим втрачаючи частину інформації - частина її "скидається".

Наприклад,

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навіть призначений для виведення? Він не має віртуальних функцій. Якщо ви походите з типу, вам доведеться мати справу з тим, що його функції членів можна викликати!
curiousguy

509

Більшість відповідей тут не пояснюють, яка насправді проблема з нарізкою. Вони пояснюють лише доброякісні випадки нарізки, а не зрадницькі. Припустимо, як і інші відповіді, що ви маєте справу з двома класами 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.


11
ІМХО, проблема полягає в тому, що існує два різних види замінюваності, які можуть мати на увазі успадкування: або будь-яке derivedзначення може бути надане коду, що очікує baseзначення, або будь-яка похідна посилання може бути використана в якості базової посилання. Я хотів би бачити мову з типовою системою, яка розглядає обидва поняття окремо. Є багато випадків, коли похідне посилання має бути заміненим на базове посилання, але похідні екземпляри не повинні бути замінені на базові; також є багато випадків, коли екземпляри повинні бути конвертованими, але посилання не повинні замінювати.
supercat

16
Я не розумію, що так погано у вашій "підступній" справі. Ви заявляли, що хочете: 1) отримати посилання на об'єкт класу A і 2) передати об’єкт b1 до класу A і скопіювати його речі до посилання класу A. Що насправді неправильно, - це правильна логіка заданий код. Іншими словами, ви взяли невеликий кадр зображення (A), розмістили його над більшим зображенням (B) і ви намалювали цей кадр, поскаржившись пізніше, що ваше велике зображення зараз виглядає некрасиво :) Але якщо ми просто розглянемо цю обрамлену область, це виглядає досить добре, так, як хотів художник, правда? :)
Младен Б.

12
Проблема полягає в тому, що C ++ за замовчуванням передбачає дуже сильний вид замінюваності - це вимагає, щоб операції базового класу правильно працювали на екземплярах підкласу. І це навіть для операцій, які компілятор автогенерував, як призначення. Тож недостатньо, щоб не викручувати власні операції з цього приводу, вам також доведеться явно відключити неправильні, створені компілятором. Або, звичайно, тримайтеся подалі від публічної спадщини, що зазвичай є гарною пропозицією вперед ;-)
fgp

14
Ще один поширений підхід - це просто відключити оператора копіювання та призначення. Для класів в межах ієрархії спадкування, як правило, немає підстав використовувати значення замість посилання або вказівника.
Сіюань Рен

13
Що за? Я не мав ідеї, що оператори можуть бути позначені віртуальними
15:15

153

Якщо у вас базовий клас 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втрачається вся особлива поведінка класу ).

40
C ++ - це не Java! Якщо wantAnA(як випливає з назви!) Хочеться A, то це і отримується. І примірник A, uh, буде поводитися як A. Як це дивно?
fgp

82
@fgp: Дивно, тому що ви не передаєте A функції.
Чорний

10
@fgp: поведінка схожа. Однак середньому програмісту на C ++ це може бути менш очевидно. Наскільки я зрозумів питання, ніхто не "скаржиться". Мова йде лише про те, як компілятор поводиться з ситуацією. Імхо, краще взагалі уникати нарізання, передаючи (const) посилання.
Чорний

8
@ThomasW Ні, я б не кидав спадщину, але використовував посилання. Якщо підпис wantAnA буде недійсним wantAnA (const A & myA) , тоді не було розрізання. Натомість передається посилання на об'єкт, що викликає, лише для читання.
Чорний

14
проблема здебільшого полягає в автоматичному кастингу, який виконує компілятор derivedдо типу A. Неявне кастинг - це завжди джерело несподіваної поведінки в C ++, тому що його часто важко зрозуміти, дивлячись на місцевий код, що відбувся акторський склад.
pqnet

41

Це все хороші відповіді. Я просто хотів би додати приклад виконання при передачі об'єктів за значенням посиланням:

#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'

Привіт. Чудова відповідь, але у мене є одне питання. Якщо я роблю щось подібне ** dev d; основа * b = & d; ** Також відбувається нарізка?
Адріан

@Adrian Якщо ви вводите деякі нові членські функції або змінні члена у похідний клас, вони недоступні безпосередньо з вказівника базового класу. Однак ви все одно можете отримати доступ до них зсередини перевантажених віртуальних функцій базового класу. Дивіться це: godbolt.org/z/LABx33
Вішал Шарма

30

Третя відповідність у Google за "C ++ нарізання" дає мені цю статтю у Вікіпедії http://en.wikipedia.org/wiki/Object_slicing і це (нагріте, але перші кілька публікацій визначають проблему): http://bytes.com/ форум / тема163565.html

Тож це коли ви присвоюєте об’єкт підкласу суперкласу. Суперклас нічого не знає про додаткову інформацію в підкласі і не має місця для його зберігання, тому додаткова інформація стає "відрізаною".

Якщо ці посилання не дають достатньо інформації для "хорошої відповіді", відредагуйте своє запитання, щоб повідомити, що ще ви шукаєте.


29

Проблема нарізки є серйозною, оскільки це може призвести до пошкодження пам’яті, і дуже важко гарантувати, що програма не страждає від неї. Щоб розробити його з мови, класи, які підтримують успадкування, повинні бути доступними лише для посилання (не за значенням). Мова програмування D має цю властивість.

Розглянемо клас A і клас B, похідний від A. Пошкодження пам'яті може статися, якщо частина A має вказівник p, а екземпляр B, який вказує p на додаткові дані B. Потім, коли додаткові дані скидаються, p вказує на сміття.


3
Поясніть, будь ласка, як може статися пошкодження пам’яті.
foraidt

4
Я забув, що копіюючий копій скине vptr, моя помилка. Але ви все одно можете отримати корупцію, якщо A має вказівник, а B встановить це, щоб вказати на розділ B, який відрізається.
Уолтер Брайт

18
Ця проблема не обмежується лише нарізкою. Будь-які класи, що містять покажчики, матимуть сумнівну поведінку з оператором призначення за замовчуванням та конструктором копій.
Weeble

2
@Weeble - Ось чому ви переосмислюєте деструктор за замовчуванням, оператор призначення та конструктор копій у цих випадках.
Bjarke Freund-Hansen

7
@Weeble: Те, що робить нарізку об’єктів гіршою, ніж загальні виправлення покажчиків, це те, що для впевненості у тому, що ви запобігли трактуванню, базовий клас повинен забезпечувати перетворення конструкторів для кожного похідного класу . (Чому? Будь-які похідні класи, які пропущені, можуть сприйматись за допомогою копіюючого копію базового класу, оскільки Derivedце неявно конвертоване Base.) Це, очевидно, суперечить принципу відкритого закриття та великому тягару з обслуговування.
j_random_hacker

10

У 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 ++.


5
" " нормальна "поведінка об'єкта ", це не "нормальна поведінка об'єкта", це довідкова семантика . І це жодним чином не пов’язане з C struct, сумісністю чи іншим безглуздим, як вам сказав будь-який випадковий священик ООП.
curiousguy

4
@curiousguy Амін, брат. Сумно бачити, як часто C ++ відбивається від того, що не є Java, коли значення семантики є однією з речей, що робить C ++ настільки шалено потужним.
fgp

Це не особливість, а не вигадка / невдача. Це нормальна поведінка при копіюванні стека, оскільки виклик функції з аргументом або (тим же) виділенням змінної стека типу Baseповинен приймати рівно sizeof(Base)байти в пам'яті з можливим вирівнюванням, можливо, саме тому "призначення" (on-stack-copy ) не буде копіювати похідних членів класу, їхні компенсації мають розмір поза. Щоб уникнути "втрати даних", просто використовуйте вказівник, як ніхто інший, оскільки пам'ять покажчика закріплена на місці та розмірі, тоді як стек дуже вольовий
Croll

Безумовно, неправильність C ++. Присвоєння похідного об'єкта базовому об'єкту повинно бути заборонено, тоді як прив'язка похідного об'єкта до посилання або вказівника базового класу має бути ОК.
Джон З. Лі

7

Отже ... Чому втрачається отримана інформація погана? ... тому що автор похідного класу, можливо, змінив подання таким чином, що відсічення додаткової інформації змінює значення, яке представляє об'єкт. Це може статися, якщо похідний клас використовується для кешування представлення, яке є більш ефективним для певних операцій, але дорогим для перетворення назад у базове подання.

Також думав, що хтось також повинен згадати, що ви повинні зробити, щоб уникнути нарізки ... Отримайте копію стандартів кодування C ++, 101 настанови правил та найкращі практики. Справа з нарізкою - №54.

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

Ви також можете позначити конструктор копій на базовій явній, що дозволяє для явного нарізання, якщо воно бажане.


3
" Ви також можете позначити конструктор копій на базі явного ", що зовсім не допомагає.
curiousguy

6

1. ВИЗНАЧЕННЯ ПРОБЛЕМИ СЛІГУВАННЯ

Якщо D є похідним класом базового класу B, то ви можете призначити об'єкт типу Похідне змінної (або параметру) типу 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

Хоча вищезазначене призначення дозволене, значення, яке присвоюється змінному вихованцеві, втрачає поле своєї породи. Це називається проблемою нарізки .

2. ЯК ВИРІШИТИ ПРОБЛЕМУ СЛІДЖЕННЯ

Щоб вирішити проблему, ми використовуємо покажчики на динамічні змінні.

ПРИКЛАД

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

У цьому випадку жоден з даних даних або функцій-членів динамічної змінної, на яку вказує ptrD (об’єкт класу спадкового), не буде втрачено. Крім того, якщо вам потрібно використовувати функції, функція повинна бути віртуальною функцією.


7
Я розумію "нарізання" частини, але не розумію "проблеми". Як це проблема, що деякий стан dogцього не входить до класу Pet( breedчлен даних) не копіюється в змінну pet? Код цікавить лише учасників Petданих - мабуть. Нарізка, безумовно, "проблема", якщо вона небажана, але я цього не бачу.
допитливий хлопець

4
" ((Dog *)ptrP)" Я пропоную скористатисяstatic_cast<Dog*>(ptrP)
цікаво,

Я пропоную зазначити, що ви змусите рядок "породи" зрештою просочити пам'ять без віртуального деструктора (деструктор 'рядок' не буде викликаний) під час видалення через "ptrP" ... Чому це те, що вам здається проблематичним? Виправлення - це переважно правильний дизайн класу. Проблема в цьому випадку полягає в тому, що записування конструкторів для контролю видимості при успадкуванні втомлює і легко забувається. З кодом ви нікуди не потрапите поблизу небезпечної зони, оскільки немає ніякого поліморфізму або навіть згадується (нарізка дозволить усікати ваш об’єкт, але не призведе до краху вашої програми тут).
Чувак

24
-1 Це повністю не може пояснити фактичну проблему. C ++ має значення семантики, а не референтну семантику, як Java, тому все це можна очікувати повністю. І "виправити" насправді є прикладом справді жахливого коду C ++. "Виправлення" неіснуючих проблем, таких як цей тип нарізки, вдаючись до динамічного розподілу, - це рецепт баггі-коду, просоченої пам'яті та жахливої ​​продуктивності. Зауважте, що є випадки, коли нарізання є поганим, але ця відповідь дає змогу вказати на них. Підказка: проблема починається, якщо ви призначаєте через посилання .
fgp

Ви навіть розумієте, що спроба отримати доступ до члена типу, який не визначений ( Dog::breed), жодним чином не ПОМИЛКА, пов’язана зі СЛІЗУВАННЯм?
Кролл

4

Мені здається, що нарізка - це не стільки інша проблема, як те, коли ваші власні класи та програма погано побудовані / розроблені.

Якщо я передаю об'єкт підкласу як параметр методу, який приймає параметр типу суперкласу, я, безумовно, повинен знати про це і знати внутрішньо, називаний метод буде працювати лише з об'єктом надкласу (він же базовим класом).

Мені здається лише необґрунтованим сподіванням, що надання підкласу, де вимагається базовий клас, якимось чином призведе до конкретних результатів підкласу, спричинить зрізання як проблему. Його або погана конструкція при використанні методу, або погана реалізація підкласу. Я здогадуюсь, що це, як правило, результат пожертвування хорошого дизайну OOP на користь доцільності чи підвищення продуктивності.


3
Але пам’ятайте, Мінок, що ви НЕ переходите в посиланнях на цей об’єкт. Ви передаєте НОВУ копію цього об’єкта, але використовуєте базовий клас, щоб скопіювати його в процесі.
Арафангіон

захищена копія / призначення базового класу, і ця проблема вирішена.
Чувак

1
Ти правий. Доброю практикою є використання абстрактних базових класів або обмеження доступу до копіювання / призначення. Однак це не так просто помітити, як тільки він там є, і про нього легко забути подбати. Виклик віртуальних методів з нарізаним * це може зробити загадкові речі, якщо ви підете без порушення доступу.
Чувак

1
Я пригадую з моїх курсів програмування C ++ в університеті, що існували найкращі практики, що для кожного класу, який ми створили, нам потрібно було написати конструктори за замовчуванням, конструктори копій та оператори присвоєння, а також деструктори. Таким чином, ви переконалися, що конструкція копій тощо - це відбулася так, як вам потрібно, під час написання класу ... а не пізніше, коли з'явилася якась дивна поведінка.
Мінок

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

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

Коли похідному класу "Об'єкт" присвоюється "Об'єкт базового класу", всі члени об'єкта похідного класу копіюються в об'єкт базового класу, крім членів, яких немає в базовому класі. Цих членів компілятор відрізає. Це називається "Обрізання об'єктів".

Ось приклад:

#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тепер схильна до невизначеної поведінки.


Я сподіваюся, що цей приклад висвітлює тих людей, які не можуть реально уявити собі речі, коли говорити про те A, Bщо вони робляться певним чином.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.