Правило 5 - використовувати його чи ні?


20

Правило 3 ( правило 5 у новому стандарті c ++) передбачає:

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

Але, з іншого боку, " чистий код " Мартіна радить видалити всі порожні конструктори та деструктори (стор. 293, G12: Захват ):

З чого використовують конструктор за замовчуванням без реалізації? Все, що вона служить, - це захаращувати код безглуздими артефактами.

Отже, як поводитися з цими двома протилежними думками? Чи слід реально реалізувати порожні конструктори / деструктори?


Наступний приклад демонструє саме те, що я маю на увазі:

#include <iostream>
#include <memory>

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    ~A(){}
    A( const A & other ) : v( new int( *other.v ) ) {}
    A& operator=( const A & other )
    {
        v.reset( new int( *other.v ) );
        return *this;
    }

    std::auto_ptr< int > v;
};
int main()
{
    const A a( 55 );
    std::cout<< "a value = " << *a.v << std::endl;
    A b(a);
    std::cout<< "b value = " << *b.v << std::endl;
    const A c(11);
    std::cout<< "c value = " << *c.v << std::endl;
    b = c;
    std::cout<< "b new value = " << *b.v << std::endl;
}

Добре компілює, використовуючи g ++ 4.6.1 з:

g++ -std=c++0x -Wall -Wextra -pedantic example.cpp

Деструктор для struct Aпорожнього не потрібен. Отже, чи має воно бути там, чи його слід зняти?


15
2 цитати розповідають про різні речі. Або я зовсім пропускаю вашу думку.
Бенджамін Баньє

1
@honk У стандарті кодування моєї команди у нас є правило завжди оголошувати всі 4 (конструктор, деструктор, конструктори копій). Мені було цікаво, чи справді це має сенс робити. Чи справді я повинен завжди декларувати деструктори, навіть якщо вони порожні?
BЈович

Щодо порожніх дектрукторів подумайте про це: codeynthesis.com/~boris/blog/2012/04/04/… . Інакше правило 3 (5) має для мене ідеальний сенс, не маю уявлення, чому б хотіли правила 4.
Бенджамін Баньє

@honk Слідкуйте за інформацією, яку ви знайдете в мережі. Не все правда. Наприклад, virtual ~base () = default;не компілюється (з поважної причини)
BЈович

@VJovic, Ні, вам не потрібно оголошувати порожній деструктор, якщо тільки вам не потрібно зробити його віртуальним. І поки ми йдемо з цього питання, ви також не повинні використовувати auto_ptrжодне.
Діма

Відповіді:


44

Для початку правило каже "напевно", тому воно не завжди застосовується.

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

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

Що стосується вашого прикладу: у такому випадку ви можете залишити деструктор поза. Очевидно, це нічого не робить. Використання розумних покажчиків - прекрасний приклад того, де і чому правило 3 не дотримується.

Це просто керівництво щодо того, де можна поглянути за другим кодом у випадку, якщо ви, можливо, забули реалізувати важливу функціональність, яку в іншому випадку ви могли пропустити.


За допомогою інтелектуальних покажчиків деструктори в більшості випадків порожні (я б сказав> 99% деструкторів у моїй кодовій базі порожні, оскільки майже кожен клас використовує ідіому pimpl).
BЈович

Ух, це стільки прищів, що я б назвав це смердючим. З багатьма компіляторами прищі буде важче оптимізувати (наприклад, складніше вбудувати).
Бенджамін Баньє

@honk Що ви маєте на увазі під "багатьма компіляторами прищі"? :)
BЈович

@VJovic: вибачте, помилка, помилка: 'код
прищів

4

Тут насправді немає протиріччя. Правило 3 говорить про деструктор, конструктор копій та оператор призначення копії. Дядько Боб розповідає про порожні конструктори за замовчуванням.

Якщо вам потрібен деструктор, то ваш клас, ймовірно, містить покажчики на динамічно виділену пам’ять, і ви, ймовірно, хочете мати ctor копії та копію, operator=()яка робить глибоку копію. Це повністю ортогонально щодо того, потрібен вам конструктор за замовчуванням чи ні.

Зауважте також, що в C ++ є ситуації, коли вам потрібен конструктор за замовчуванням, навіть якщо він порожній. Скажімо, ваш клас має конструктор за замовчуванням. У цьому випадку компілятор не створить для вас конструктор за замовчуванням. Це означає, що об'єкти цього класу не можуть зберігатися в контейнерах STL, оскільки ці контейнери очікують, що об'єкти можуть бути сконструйовані за замовчуванням.

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


2

Тут ваш потенціал (*), еквівалентний конструктору / призначенню / деструктору за замовчуванням, має на меті: задокументувати факт, який у вас є, про проблему та визначити, що поведінка за замовчуванням була правильною. До речі, в C ++ 11 речі не стабілізувалися, щоб знати, чи =defaultможе служити цій меті.

(Є й інша потенційна мета: надати визначення поза рядка замість вбудованого за замовчуванням, краще документувати явно, якщо у вас є якісь причини для цього).

(*) Потенціал, тому що я не пам’ятаю випадків із реального життя, коли правило трьох не застосовувалось, якщо мені довелося щось робити в одному, я повинен був щось робити в інших.


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


Приклад демонструє мою точку зору. Деструктор насправді не потрібен, але правило 3 говорить, що воно повинно бути там.
BЈович

1

Правило 5 - це какутативне розширення правила 3, яке є категативною поведінкою проти можливого неправильного використання об'єкта.

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

Оскільки копіювати, призначати, переміщувати та переносити за значеннями копіювання за замовчуванням , якщо ви не маєте лише значень , ви повинні визначити, що робити.

При цьому, C ++ видаляє ці копії, якщо ви визначаєте переміщення, і видаляє переміщення, якщо ви визначаєте копію. У більшості випадків вам потрібно визначити, чи хочете ви наслідувати значення (отже, копія mut клонує ресурс, а переміщення не має сенсу) або менеджер ресурсів (і, отже, переміщувати ресурс, де копія не має сенсу: правило з 3 стає правилом для інших 3 )

Випадки, коли вам потрібно визначити як копіювання, так і переміщення (правило 5), є досить рідкісними: зазвичай у вас є "велике значення", яке має бути скопійовано, якщо надано окремим об'єктам, але їх можна перемістити, якщо взяти з тимчасового об'єкта (уникаючи клон потім знищити ). Це стосується контейнерів STL або арифметичних контейнерів.

Випадок може бути матрицями: вони повинні підтримувати копію, оскільки вони є значеннями ( a=b; c=b; a*=2; b*=3;не повинні впливати один на одного), але їх можна оптимізувати, підтримуючи також переміщення ( a = 3*b+4*cмає такий, +що займає два темпорації та генерує тимчасове: уникати клонування та видаляти можна корисно)


1

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

Визначаючи це як односторонній взаємозв'язок із деструктором, стає ясніше декілька речей:

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

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


-3

Є ще один момент, про який ще не говорилося в дискусії: Деструктор завжди повинен бути віртуальним.

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    virtual ~A(){}
    ...
}

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

Якщо ви поставите всі попередження на (-Wall -Wextra -Weffc ++), g ++ попередить вас про це. Я вважаю хорошою практикою завжди оголошувати віртуального деструктора в будь-якому класі, тому що ніколи не знаєш, чи з часом ваш клас стане базовим. Якщо віртуальний деструктор не потрібен, це не шкодить. Якщо це так, ви економите час, щоб знайти помилку.


1
Але я не хочу віртуального конструктора. Якщо я це роблю, то кожен виклик будь-якого методу використовував би віртуальну розсилку. btw візьміть на замітку, що в c ++ немає такого поняття, як "віртуальний конструктор". Також я склав приклад як дуже високий рівень попередження.
BЈоviћ

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