Всегда ли вызов деструктора является признаком плохого дизайна?

Я думал: они говорят, что если вы вызываете деструктор вручную - вы делаете что-то не так. Но так ли это всегда? Есть ли контрпримеры? Ситуации, когда необходимо вызвать его вручную или когда трудно /невозможно /нецелесообразно избежать этого?

71 голос | спросил Violet Giraffe 7 Jam1000000amMon, 07 Jan 2013 01:33:21 +040013 2013, 01:33:21

13 ответов


0

Вызов деструктора вручную необходим, если объект был создан с использованием перегруженной формы operator new(), за исключением случаев использования «std::nothrow "перегрузки:

T* t0 = new(std::nothrow) T();
delete t0; // OK: std::nothrow overload

void* buffer = malloc(sizeof(T));
T* t1 = new(buffer) T();
t1->~T(); // required: delete t1 would be wrong
free(buffer);

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

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

ответил Dietmar Kühl 7 Jam1000000amMon, 07 Jan 2013 01:39:28 +040013 2013, 01:39:28
0

Все ответы описывают конкретные случаи, но есть общий ответ:

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

Обычно это происходит во всех ситуациях, когда распределение /освобождение памяти управляется независимо от строительства /разрушения объекта. В этих случаях конструирование происходит посредством размещения нового на существующий кусок памяти, а уничтожение происходит с помощью явного вызова dtor.

Вот сырой пример:

{
  char buffer[sizeof(MyClass)];

  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }
  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }

}

Еще одним примечательным примером является std::allocator по умолчанию при использовании std::vector: элементы создаются в vector во время push_back, но память распределяется по блокам, поэтому в ней уже существует конструкция элемента. И, следовательно, vector::erase должно уничтожить элементы, но не обязательно, что это освобождает память (особенно, если новый push_back должен произойти в ближайшее время ...) .

Это «плохой дизайн» в строгом смысле ООП (вы должны управлять объектами, а не памятью: объектам факта требуется память - это «инцидент»), это «хороший дизайн» в «низкоуровневом программировании» или в случаях где память не берется из «бесплатного хранилища», по умолчанию покупается operator new.

Это плохой дизайн, если это происходит случайно в коде, это хороший дизайн, если это происходит локально с классами, специально разработанными для этой цели.

ответил Emilio Garavaglia 7 Jam1000000amMon, 07 Jan 2013 02:19:01 +040013 2013, 02:19:01
0

Нет, зависит от ситуации, иногда это законный и хороший дизайн.

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

Чтобы создать объект динамически, T* t = new T; под капотом: 1. Размер памяти (T) выделен. 2. Конструктор T вызывается для инициализации выделенной памяти. Оператор new делает две вещи: выделение и инициализация.

Чтобы уничтожить объект delete t; под колпаком: 1. Вызывается деструктор T. 2. память, выделенная для этого объекта освобождается. Оператор удаления также делает две вещи: уничтожение и освобождение.

Один пишет конструктор для инициализации и деструктор для уничтожения. Когда вы явно вызываете деструктор, выполняется только уничтожение, но не освобождение .

Таким образом, допустимым использованием явного вызова деструктора может быть: «Я только хочу уничтожить объект, но я не (или не могу) освободить выделение памяти (пока)».

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

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

ответил 7 Jam1000000amMon, 07 Jan 2013 03:48:47 +040013 2013, 03:48:47
0

Как указано в FAQ, вы должны явно вызывать деструктор при размещение нового .

  

Это единственный раз, когда вы явно вызываете деструктор.

Я согласен, хотя это редко требуется.

ответил Luchian Grigore 7 Jam1000000amMon, 07 Jan 2013 01:39:19 +040013 2013, 01:39:19
0

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

Eg.

{
  Class c;
  c.~Class();
}

Если вам действительно необходимо выполнить те же операции, у вас должен быть отдельный метод.

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

ответил Jack 7 Jam1000000amMon, 07 Jan 2013 01:35:44 +040013 2013, 01:35:44
0

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

ответил James Kanze 7 Jam1000000amMon, 07 Jan 2013 01:56:37 +040013 2013, 01:56:37
0

Бывают случаи, когда они необходимы:

В коде, над которым я работаю, я использую явный вызов деструктора в распределителях, у меня есть реализация простого распределителя, который использует размещение new для возврата блоков памяти в контейнеры stl. У меня в разрушении есть:

  void destroy (pointer p) {
    // destroy objects by calling their destructor
    p->~T();
  }

в то время как в конструкции:

  void construct (pointer p, const T& value) {
    // initialize memory with placement new
    #undef new
    ::new((PVOID)p) T(value);
  }

также выполняется выделение в allocate () и освобождение памяти в deallocate () с использованием механизмов выделения и освобождения платформы. Этот распределитель использовался для обхода doug lea malloc и непосредственного использования, например, LocalAlloc для windows.

ответил marcinj 7 Jam1000000amMon, 07 Jan 2013 01:44:09 +040013 2013, 01:44:09
0

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

class MyClass {
  HANDLE h1,h2;
  public:
  MyClass() {
    // handles have to be created first
    h1=SomeAPIToCreateA();
    h2=SomeAPIToCreateB();
    ...
    try {
      if(error) {
        throw MyException();
      }
    }
    catch(...) {
      this->~MyClass();
      throw;
    }
  }
  ~MyClass() {
    SomeAPIToDestroyA(h1);
    SomeAPIToDestroyB(h2);
  }
};
ответил CITBL 4 PM00000030000004131 2016, 15:44:41
0

Я никогда не сталкивался с ситуацией, когда нужно вызывать деструктор вручную. Кажется, я помню, что даже Страуструп утверждает, что это плохая практика.

ответил Lieuwe 7 Jam1000000amMon, 07 Jan 2013 01:36:11 +040013 2013, 01:36:11
0

Я нашел 3 случая, когда мне нужно было сделать это:

  • распределение /освобождение объектов в памяти, созданной memory-mapped-io или разделяемой памятью
  • при реализации данного интерфейса C с использованием C ++ (да, к сожалению, это все еще происходит сегодня (потому что у меня недостаточно влияния, чтобы изменить его))
  • при реализации классов-распределителей
ответил 14 Jpm1000000pmThu, 14 Jan 2016 23:27:16 +030016 2016, 23:27:16
0

Память ничем не отличается от других ресурсов: вы должны взглянуть на http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Keynote-Bjarne-Stroustrup-Cpp11-Style особенно в той части, где Бьярн говорит о RAII (около 30 минут)

Все необходимые шаблоны (shared_ptr, unique_ptr, weak_ptr) являются частью стандартной библиотеки C ++ 11

ответил xol 7 Jam1000000amMon, 07 Jan 2013 02:28:52 +040013 2013, 02:28:52
0

Найден другой пример, где вам придется вызывать деструктор (ы) вручную. Предположим, вы реализовали вариантный класс, который содержит один из нескольких типов данных:

struct Variant {
    union {
        std::string str;
        int num;
        bool b;
    };
    enum Type { Str, Int, Bool } type;
};

Если экземпляр Variant содержал std::string, и теперь вы присваиваете объединению другой тип, сначала вы должны уничтожить std::string. Компилятор не будет делать это автоматически .

ответил Violet Giraffe 30 J0000006Europe/Moscow 2018, 12:15:14
0

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

При написании метода типа «Reset» для восстановления объекта до его начального состояния вполне разумно вызывать Destructor для удаления старых данных, которые сбрасываются.

class Widget
{
private: 
    char* pDataText { NULL  }; 
    int   idNumber  { 0     };

public:
    void Setup() { pDataText = new char[100]; }
    ~Widget()    { delete pDataText;          }

    void Reset()
    {
        Widget blankWidget;
        this->~Widget();     // Manually delete the current object using the dtor
        *this = blankObject; // Copy a blank object to the this-object.
    }
};
ответил abelenky 25 Maypm16 2016, 16:48:41

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132