Чи дозволено видалити це?


232

Чи дозволено, delete this;якщо оператор delete є останнім оператором, який буде виконаний на цьому екземплярі класу? Звичайно, я впевнений, що об'єкт, представлений thisточкою вказівника, newстворений ly.

Я думаю про щось подібне:

void SomeModule::doStuff()
{
    // in the controller, "this" object of SomeModule is the "current module"
    // now, if I want to switch over to a new Module, eg:

    controller->setWorkingModule(new OtherModule());

    // since the new "OtherModule" object will take the lead, 
    // I want to get rid of this "SomeModule" object:

    delete this;
}

Чи можу я це зробити?


13
Головною проблемою було б те, що якщо ви delete thisстворили щільну зв'язок між класом та методом розподілу, який використовується для створення об’єктів цього класу. Це дуже поганий дизайн ОО, оскільки найосновніше в ООП - це робити автономні класи, які не знають і не цікавляться тим, що робить їхній абонент. Таким чином, правильно розроблений клас не повинен знати або дбати про те, як він був виділений. Якщо вам чомусь потрібен такий своєрідний механізм, я думаю, що кращим дизайном було б використовувати клас обгортки навколо фактичного класу, і дозволити обгортці займатися розподілом.
Лундін

Ви не можете видалити повідомлення setWorkingModule?
Джиммі Т.

Відповіді:


238

C ++ FAQ Lite має запис спеціально для цього

Я думаю, ця цитата гарно резюмує

Поки ви обережні, це нормально, щоб об'єкт покінчив життя самогубством (видаліть це).


15
Відповідна FQA також має корисний коментар: yosefk.com/c++fqa/heap.html#fqa-16.15
Олександр С.

1
Для безпеки ви можете використовувати приватний деструктор на оригінальному об'єкті, щоб переконатися, що його не побудовано на стеку або як частина масиву чи вектора.
Cem Kalyoncu

Визначте "обережно"
CinCout

3
"Обережно" визначено у пов'язаній статті FAQ. (Хоча посилання на FQA здебільшого скачується - як і майже все в ньому - наскільки C ++ поганий)
CharonX

88

Так, delete this;має визначені результати, доки (як ви вже зазначали) ви запевняєте, що об'єкт розподілявся динамічно, і (звичайно) ніколи не намагайтеся використовувати об'єкт після його знищення. Протягом багатьох років було задано багато питань про те, що конкретно йде про стандарт delete this;, на відміну від видалення якогось іншого вказівника. Відповідь на це досить коротка і проста: вона нічого не говорить. Це просто говорить, що deleteоперанд повинен бути виразом, який позначає вказівник на об'єкт або масив об'єктів. Він детально описує такі речі, як те, як з'ясовує, яку (якщо є) функцію розміщення, щоб викликати звільнення пам'яті, але весь розділ наdelete (§ [expr.delete]) взагалі не згадується delete this;конкретно. У розділі про деструктори згадуєтьсяdelete this в одному місці (§ [class.dtor] / 13):

У точці визначення віртуального деструктора (включаючи неявне визначення (15.8)) функція делокації без масиву визначається так, як якщо б вираз видалити це, що з’являється у невіртуальному деструкторі класу деструктора (див. 8.3.5 ).

Це, як правило, підтримує думку про те, що стандарт вважає delete this;дійсним - якби він був недійсним, його тип не мав би сенсу. Це єдине місце, про яке стандарт згадує delete this;взагалі, наскільки я знаю.

У будь-якому випадку, деякі вважають delete thisзлий хакер і говорять будь-кому, хто слухатиме, що цього слід уникати. Однією з часто цитованих проблем є складність забезпечення того, щоб об'єкти класу тільки коли-небудь динамічно розподілялися. Інші вважають це цілком розумною ідіомою і користуються нею постійно. Особисто я десь посередині: я рідко використовую це, але не соромтеся робити це, коли це здається правильним інструментом для роботи.

Первинний час, коли ви використовуєте цю техніку, - це об'єкт, який має життя майже повністю. Одним із прикладів, який наводив Джеймс Канзе, була система виставлення рахунків / відстеження, над якою він працював у телефонній компанії. Коли ви починаєте телефонувати, то щось це враховує і створює phone_callоб'єкт. З цього моменту phone_callоб'єкт обробляє деталі телефонного дзвінка (здійснює з'єднання під час набору номера, додаючи запис до бази даних, щоб сказати, коли виклик розпочався, можливо, підключіть більше людей, якщо ви робите конференційний дзвінок тощо). Останні люди під час виклику зависають,phone_call об'єкт робить остаточне ведення обліку (наприклад, додає запис у базу даних, щоб сказати, коли ви повісили трубку, щоб вони могли обчислити, як довго тривав ваш дзвінок), а потім знищує себе. Термін експлуатаціїphone_callОб'єкт ґрунтується на тому, коли перша людина починає дзвінок і коли останні люди залишають виклик - з точки зору решти системи, це в основному абсолютно довільно, тому ви не можете прив'язувати її до будь-якої лексичної сфери в коді , або що-небудь у цьому порядку.

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


2
Спасибі, я покладу це десь у своїй пам’яті. Я думаю, ви визначаєте конструктори та деструктори як приватні та використовуєте якийсь статичний заводський метод для створення таких об'єктів.
Олександр К.

@Alexandre: Ви, мабуть, зробили це в більшості випадків так чи інакше - я не знаю ніде близько всіх деталей системи, над якою він працював, тому не можу сказати напевно про це.
Джеррі Труну

Мені часто подолається проблема, як розподіляється пам'ять, - це включити bool selfDeleteпараметр у конструктор, який присвоюється змінній члена. Зрозуміло, це означає передавати програмісту достатньо мотузки, щоб зав'язати в ньому петлю, але я вважаю, що кращим є витік пам'яті.
MBraedley

1
@MBraedley: Я зробив те саме, але вважаю за краще уникати того, що мені здається неприємним.
Джеррі Труну

Для тих, хто може хвилюватись ... є досить хороший шанс, що з ним обробляється (принаймні частково) за кодом, який точно відповідає this. Так, з кодом обробляється точно this. ;)
Галактика

46

Якщо вас це лякає, є абсолютно законний злом:

void myclass::delete_me()
{
    std::unique_ptr<myclass> bye_bye(this);
}

Я думаю, що delete thisце ідіоматичний C ++, і це я представляю лише як цікавість.

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

void myclass::throw_error()
{
    std::unique_ptr<myclass> bye_bye(this);
    throw std::runtime_exception(this->error_msg);
}

Примітка: якщо ви використовуєте компілятор, старший за C ++ 11, який ви можете використовувати std::auto_ptrзамість std::unique_ptr, це зробить те саме.


Я не можу зробити це компілювати за допомогою c ++ 11, чи є якісь спеціальні варіанти компілятора для цього? Також не потрібно переміщення цього вказівника?
Сова

@Але не впевнений, що ти маєш на увазі, це працює для мене: ideone.com/aavQUK . Створення unique_ptrз іншого unique_ptr вимагає переміщення, але не з необробленого вказівника. Якщо все не змінилося в C ++ 17?
Марк Викуп

Ах C ++ 14, тому і буде. Мені потрібно оновити свій c ++ у вікні свого розробника. Я спробую знову сьогодні ввечері на своїй нещодавно створеній системі gentoo!
Сова

25

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

Навіть якщо ви заздалегідь знаєте, що ваш поточний план - виділити лише один екземпляр на купі, що робити, якщо в майбутньому прийде якийсь розробник, який пощастить, і вирішить створити екземпляр у стеці? Або що робити, якщо він вирізає та вставить певні частини класу до нового класу, який він має намір використати у стеку? Коли код досягне «видалити це», він вимкнеться та видалить його, але тоді, коли об’єкт вийде за межі, він викличе деструктор. Потім деструктор спробує видалити його ще раз, і тоді вам потрібно ввести шланг. Раніше щось подібне призвело б до перезавантаження не лише програми, але й операційної системи та комп'ютера. У будь-якому випадку цього настійно НЕ рекомендується, і його майже завжди слід уникати. Я мав би бути відчайдушним, серйозно замазаний,


7
+1. Я не можу зрозуміти, чому тебе зневажали. "C ++ слід писати так, щоб він працював, чи клас інстанціюється на купі, масиві чи на стеці" - це дуже хороша порада.
Джон

1
Ви можете просто загортати об’єкт, який ви хочете видалити, у спеціальний клас, який видаляє об'єкт, а потім і сам, і використовувати цю техніку для запобігання розподілу стеків: stackoverflow.com/questions/124880/… Бувають випадки, коли насправді не існує життєздатна альтернатива. Я просто використав цю техніку для самостійного видалення потоку, який запускається функцією DLL, але функція DLL повинна повернутися до кінця потоку.
Фелікс Домбек

Ви не можете програмувати таким чином, щоб хтось, хто просто копіює та вставляв ваш код, все одно зловживав ним
Jimmy T.

22

Це дозволено (просто не використовуйте об'єкт після цього), але я б не писав такого коду на практиці. Я думаю , що delete thisмає з'явитися тільки в функціях , які називаються releaseабо Releaseі виглядає наступним чином : void release() { ref--; if (ref<1) delete this; }.


Що саме один раз у кожному моєму проекті ... :-)
cmaster - відновіть моніку

15

Добре, що в Component Object Model (COM) delete thisпобудова може бути частиною Releaseметоду, який викликається, коли потрібно випустити придбаний об'єкт:

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}

8

Це основна ідіома для обчислених посилань об'єктів.

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

Коли остання посилання скидає стек або видаляється, кількість посилань перейде до нуля. Тоді поведінка вашого об'єкта за замовчуванням буде закликом "видалити це" для збирання сміття - бібліотеки, які я пишу, забезпечують захищений віртуальний виклик "CountIsZero" в базовому класі, щоб ви могли змінити цю поведінку для таких речей, як кешування.

Ключовим фактором для забезпечення безпеки є не надання доступу користувачам до КОНСТРУКТОРУ відповідного об'єкта (зробити його захищеним), а натомість змусити їх викликати статичного члена - типу FACTORY - "статичний довідник CreateT (...)". Таким чином, ви точно знаєте, що вони завжди побудовані зі звичайним "новим" і що жодного необробленого вказівника не буде, тому "видалити це" ніколи не підірветься.


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

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

7

Ви можете це зробити. Однак ви не можете призначити це. Тому причина, яку ви заявляєте про це, "я хочу змінити погляд", видається дуже сумнівною. На мій погляд, кращим методом було б те, щоб об’єкт, який містить погляд, замінив цей погляд.

Звичайно, ви використовуєте RAII-об’єкти, і тому насправді взагалі не потрібно викликати видалення ... правда?


4

Це старе, відповів на запитання, але @Alexandre запитав "Чому хтось хотів би це зробити?", І я подумав, що я можу навести приклад використання, який я розглядаю сьогодні вдень.

Спадковий код. Використовує голі вказівники Obj * obj із видаленням obj в кінці.

На жаль, мені потрібно часом, не часто, щоб довше зберегти живий об’єкт.

Я розглядаю, як зробити його посиланням, підрахувавши розумний покажчик. Але було б багато коду для зміни, якби я використовував ref_cnt_ptr<Obj>всюди. І якщо ви змішуєте голий Obj * і ref_cnt_ptr, ви можете отримати об'єкт неявно видалений, коли останній ref_cnt_ptr відходить, навіть якщо Obj * ще живий.

Тому я думаю про створення явного_делето_реф_cnt_ptr. Тобто довідковий лічильний покажчик, де видалення виконується лише в явній процедурі видалення. Використовуючи його в тому місці, де існуючий код знає термін експлуатації об'єкта, а також у моєму новому коді, який довше зберігає живий об’єкт.

Збільшення та зменшення кількості відліку, як явний_delete_ref_cnt_ptr, маніпулюють.

Але НЕ звільняється, коли в деструкторі express_delete_ref_cnt_ptr бачення посилань дорівнює нулю.

Вивільняється лише тоді, коли під час явної операції, схожої на видалення, посилання вважається рівним нулю. Наприклад, у чомусь подібному:

template<typename T> class explicit_delete_ref_cnt_ptr { 
 private: 
   T* ptr;
   int rc;
   ...
 public: 
   void delete_if_rc0() {
      if( this->ptr ) {
        this->rc--;
        if( this->rc == 0 ) {
           delete this->ptr;
        }
        this->ptr = 0;
      }
    }
 };

Гаразд, щось подібне. Дещо незвично, щоб тип посилання, що нараховується, не видаляв автоматично об'єкт, на який вказується деструктор rc'ed ptr. Але здається, що це може зробити комбінування голих покажчиків та rc'ed покажчиків трохи безпечніше.

Але поки видаляти це не потрібно.

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

class Pointee { 
 private: 
   int rc;
   ...
 public: 
   void delete_if_rc0() {
        this->rc--;
        if( this->rc == 0 ) {
           delete this;
        }
      }
    }
 };

Насправді, він взагалі не повинен бути членом, але може бути вільною функцією:

map<void*,int> keepalive_map;
template<typename T>
void delete_if_rc0(T*ptr) {
        void* tptr = (void*)ptr;
        if( keepalive_map[tptr] == 1 ) {
           delete ptr;
        }
};

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


0

Видалити це законно, доки об’єкт знаходиться в купі. Вам потрібно буде вимагати, щоб об'єкт був лише купи. Єдиний спосіб зробити це - захистити деструктор - таким чином видалення може бути названо ТІЛЬКИ з класу, тому вам знадобиться метод, який забезпечить видалення

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