Анотація базових класів та побудова копій, правила


10

Часто буває корисно мати абстрактний базовий клас, щоб ізолювати інтерфейс об'єкта.

Проблема полягає в тому, що конструкція копій, IMHO, значно забита за замовчуванням у C ++, при цьому конструктори копій генеруються за замовчуванням.

Отже, що таке гетчі, коли у вас є абстрактний базовий клас та вихідні вказівники у похідних класах?

class IAbstract
{
    ~IAbstract() = 0;
}

class Derived : public IAbstract
{
    char *theProblem;
    ...
}

IAbstract *a1 = new Derived();
IAbstract a2 = *a1;//???

А тепер ви чітко відключите побудову копій для всієї ієрархії? Визначити конструкцію копії приватною IAbstract?

Чи є правила з трьох із абстрактними базовими класами?


1
використовуйте посилання замість покажчиків :)
tp1

@ tp1: або принаймні якийсь розумний вказівник.
Бенджамін Баньє

1
Іноді просто потрібно працювати з існуючим кодом ... Ви не можете змінити все за мить.
Кодер

Чому ви вважаєте, що конструктор копій за замовчуванням зламаний?
BЈович

2
@Coder: Посібник зі стилю Google - це купа сміття та абсолютно підходить для будь-якої розробки на C ++.
DeadMG

Відповіді:


6

Конструкція копіювання на абстрактному класі в більшості випадків повинна бути приватною, а також оператором присвоєння.

Абстрактні заняття, за визначенням, зроблені як поліморфний тип. Таким чином, ви не знаєте, скільки пам’яті використовує ваш екземпляр, і тому ви не можете його безпечно скопіювати або призначити. На практиці ви ризикуєте нарізати: /programming/274626/what-is-the-slicing-problem-in-c

Поліморфним типом у C ++ не слід керувати значенням. Ви маніпулюєте ними за посиланням або за вказівником (або будь-яким розумним вказівником).

З цієї причини Java зробила об'єкт маніпулюваним лише посиланням, і чому C # і D мають поділ між класами та структурами (перший - поліморфний та референсний тип, другий - не поліморфний та тип значення).


2
Речі на Java не кращі. На Java копіювати що завгодно, навіть коли вам це справді дуже потрібно, і дуже легко забути це зробити. Отже, у вас виникають неприємні помилки, коли дві структури даних мають посилання на один і той же клас значень (наприклад, Дата), а потім одна з них змінює Дату, а інша структура даних тепер порушена.
Кевін Клайн

3
Мех. Це не проблема - той, хто знає C ++, не помилиться. Що ще важливіше, взагалі немає підстав маніпулювати поліморфними типами за значенням, якщо ви знаєте, що робите. Ви ініціюєте загальну реакцію на коліна через технічно малу можливість. Показники NULL є більшою проблемою.
DeadMG

8

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


А що робити, якщо існує абстрактна -> Похідна1 -> Похідна2 ієрархія, а Derived2 призначений Derived1? Ці темні куточки мови все ще мене дивують.
Кодер

@Coder: Це зазвичай розглядають як PEBKAC. Однак, безпосередньо, ви завжди можете оголосити приватну особу operator=(const Derived2&)в Derived1.
DeadMG

Гаразд, можливо, це насправді не проблема. Я просто хочу переконатися, що клас безпечний від зловживань.
Кодер

2
І за це ви повинні отримати медаль.
Всесвітній інженер

2
@Coder: Зловживання ким? Найкраще, що ви можете зробити, це спростити запис правильного коду. Безглуздо намагатися захищатись від програмістів, які не знають C ++.
Кевін Клайн

2

Зробивши ctor і призначення приватними (або оголосивши їх як = видалити в C ++ 11), ви відключите копію.

Суть у тому, де вам це потрібно зробити. Залишатися зі своїм кодом IAb Abstract не є проблемою. (зауважте, що, зробивши те, що ви зробили, ви присвоюєте *a1 IAbstractсубект a2, втрачаючи будь-яку посилання на Derived. Присвоєння значення не є поліморфним)

Питання постає з Derived::theproblem. Копіювання отриманого в інший може насправді ділитись *theproblemданими, які можуть не бути призначеними для спільного доступу (є два екземпляри, які можуть викликати delete theproblemу своєму деструкторі).

Якщо це так, воно Derivedповинно бути не копіюваним і не присвоюватися. Звичайно, якщо ви зробите приватну копію IAbstract, оскільки копія за замовчуванням Derivedпотрібна, Derivedвона також не підлягає копіюванню. Але якщо ви визначите свої, Derived::Derived(const Derived&)не викликаючи IAbtractкопії, ви все одно можете скопіювати їх.

Проблема в Derived, і рішення повинно залишатися в Derived: якщо він повинен бути лише динамічним об'єктом, до якого звертаються лише вказівники або посилання, він повинен бути отриманий самим

class Derived
{
    ...
    Derived(const Derived&) = delete;
    Derived& operator=(const Derived&) = delete;
};

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

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