2016-04-14 4 views
-1

Рассмотрим мы создаем массив, используя этот путь:Удалить массив классов без вызова деструкторов

T* arr = new T[num]; 

И теперь из-за некоторых причин, мы поняли, что нам нужно просто удалить этот массив, но не вызывая никаких T деструкторов.

Мы все знаем, что если мы пишем следующий:

delete arr; 

T деструктор будет вызван. Если мы пишем это:

delete[] arr; 

в num деструкторов будет называться. Играя с указателями, вы понимаете, что new вставляет перед указателем результата значение unsigned long long, которое представляет количество выделенных T экземпляров. Поэтому мы пытаемся перехитрить C++, пытаясь изменить это значение на количество байтов, которое arr занимает и удаляет его как (char*) в надежде, что в этом случае delete не будет называть деструкторы для экземпляров T и просто свободной занятой памяти. Так что вы пишете что-то вроде этого:

typedef unsigned long long; 
unsll & num = *(unsll)((char*)arr-sizeof(unsll)); 
num = num*sizeof(T); 
delete ((char*)arr); 

Но это не работает, и C++ создает триггер прерывания (запустить ошибки времени) при попытке удалить это. Так что это не сработает. И многое другое, играя с указателями, не работает, по крайней мере, происходит некоторая ошибка (компиляция или время выполнения). Так что вопрос:

Можно ли удалить массив классов в C++ без вызова их деструкторов?

+2

'delete arr' - это неопределенное поведение; он может сделать что угодно. –

+3

Почему вы не хотите называть деструкторов? Это, как правило, очень плохая идея. –

+0

* Итак, мы пытаемся перехитрить C++ * - Почему вы хотите «перехитрить C++»?- * вы понимаете, что новые вставки перед указателем результата имеют значение unsigned long long, которое представляет количество выделенных T-экземпляров * - Это новости для меня. – PaulMcKenzie

ответ

2

Возможно, вы хотите ::operator delete[](arr).

(см http://en.cppreference.com/w/cpp/memory/new/operator_delete)

Но это до сих пор не определено поведение, и это ужасная идея.

+0

Где неопределенное поведение? –

+0

Завершение жизни (освобождение памяти) без вызова деструктора - это UB, я верю. (Глава и стих, чтобы не ругать.) –

+0

ОК, я думаю, вы ссылаетесь на [basic.life]/4 "[...] любая программа, зависящая от побочных эффектов, создаваемых деструктором, имеет неопределенное поведение». Таким образом, это будет зависеть от того, какие содержащиеся в нем деструкторы OP. –

1

Одним из простых способов освобождения без вызова деструкторов является разделение выделения и инициализация. При правильном уходе за выравниванием вы можете использовать размещение new (или функциональность стандартного объекта распределителя) для создания экземпляров объекта внутри выделенного блока. Затем в конце вы можете просто освободить блок, используя соответствующую функцию освобождения.

Я не могу придумать ни одной ситуации, когда это было бы разумно: она сильно пахнет преждевременной оптимизацией и проблемой X/Y (имея дело с проблемой X, воображая непрактичное Y как решение, а затем спрашивая только о Y).

-expression предназначен для совместного распределения с инициализацией, так что они выполняются как операция «все или ничего». Эта муфта, а также муфта для очистки и освобождения, является ключом к правильности, и это также упрощает многое (т. Е. Внутри есть сложность, с которой не приходится иметь дело). У развязки должна быть очень веская причина. Избегание вызовов деструктора, например. цели оптимизации, не является веской причиной.

0

Я только собираюсь обратиться ваш конкретный вопрос

Это можно удалить массив классов в C++, не называя их деструкторы?

Короткий ответ да.

Долгий ответ: да, но есть оговорки и, учитывая, что деструктор для (т. Е. Очистка ресурсов), в общем случае это плохая идея избежать вызова деструктора класса.

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

TL; DR, если вам нужно, чтобы избежать деструкторов, вы должны пересмотреть свой дизайн (то есть использовать для копирования/перемещения семантики или контейнер STL вместо этого).

Вы можете использовать malloc и free избежать вызовов конструктора и деструктора, пример кода:

#include <iostream> 
#include <cstdio> 

class MyClass { 
    public: 
     MyClass() : m_val(0) 
     { 
      this->init(42); 
      std::cout << "ctor" << std::endl; 
     } 

     ~MyClass() 
     { 
      std::cout << "dtor" << std::endl; 
     } 

     friend std::ostream& operator<<(std::ostream& stream, const MyClass& val) 
     { 
      stream << val.m_val; 
      return stream; 
     } 

     void init(int val) 
     { 
      /* just showing that the this pointer is valid and can 
      reference private members regardless of new or malloc */ 
      this->_init(val); 
     } 

    private: 
     int m_val; 

     void _init(int val) 
     { 
      this->m_val = val; 
     } 
}; 

template < typename Iterator > 
void print(Iterator begin, Iterator end) 
{ 
    while (begin != end) { 
     std::cout << *begin << std::endl; 
     ++begin; 
    } 
} 

void set(MyClass* arr, std::size_t count) 
{ 
    for (; count > 0; --count) { 
     arr[count-1].init(count); 
    } 
} 

int main(int argc, char* argv[]) 
{ 
    std::cout << "Calling new[10], 10 ctors called" << std::endl; 
    MyClass* arr = new MyClass[10]; // 10 ctors called; 
    std::cout << "0: " << *arr << std::endl; 
    set(arr, 10); 
    print(arr, arr+10); 
    std::cout << "0: " << *arr << std::endl; 
    std::cout << "Calling delete[], 10 dtors called" << std::endl; 
    delete[] arr; // 10 dtors called; 

    std::cout << "Calling malloc(sizeof*10), 0 ctors called" << std::endl; 
    arr = static_cast<MyClass*>(std::malloc(sizeof(MyClass)*10)); // no ctors 
    std::cout << "0: " << *arr << std::endl; 
    set(arr, 10); 
    print(arr, arr+10); 
    std::cout << "0: " << *arr << std::endl; 
    std::cout << "Calling free(), 0 dtors called" << std::endl; 
    free(arr); // no dtors 

    return 0; 
} 

Следует отметить, что смешивание new с free и/или malloc с delete приводит неопределенному behavoir, так вызов MyClass* arr = new MyClass[10];, а затем позвонить free(arr); может не работать как «ожидаемый» (отсюда UB).

Другая проблема, которая возникнет из-за не вызова конструктора/деструктора в C++, связана с наследованием. Вышеприведенный код будет работать с malloc и free для базовых классов, но если вы начнете бросать более сложные типы или наследовать от других классов, конструкторы/деструкторы унаследованных классов не будут вызваны, а вещи станут уродливыми реальными, например :

#include <iostream> 
#include <cstdio> 

class Base { 
    public: 
     Base() : m_val(42) 
     { 
      std::cout << "Base()" << std::endl; 
     } 

     virtual ~Base() 
     { 
      std::cout << "~Base" << std::endl; 
     } 

     friend std::ostream& operator<<(std::ostream& stream, const Base& val) 
     { 
      stream << val.m_val; 
      return stream; 
     } 

    protected: 
     Base(int val) : m_val(val) 
     { 
      std::cout << "Base(" << val << ")" << std::endl; 
     } 

     void _init(int val) 
     { 
      this->m_val = val; 
     } 

     int m_val; 
}; 

class Child : public virtual Base { 
    public: 
     Child() : Base(42) 
     { 
      std::cout << "Child()" << std::endl; 
     } 

     ~Child() 
     { 
      std::cout << "~Child" << std::endl; 
     } 

     void init(int val) 
     { 
      this->_init(val); 
     } 
}; 

template < typename Iterator > 
void print(Iterator begin, Iterator end) 
{ 
    while (begin != end) { 
     std::cout << *begin << std::endl; 
     ++begin; 
    } 
} 

void set(Child* arr, std::size_t count) 
{ 
    for (; count > 0; --count) { 
     arr[count-1].init(count); 
    } 
} 

int main(int argc, char* argv[]) 
{ 
    std::cout << "Calling new[10], 20 ctors called" << std::endl; 
    Child* arr = new Child[10]; // 20 ctors called; 
    // will print the first element because of Base::operator<< 
    std::cout << "0: " << *arr << std::endl; 
    set(arr, 10); 
    print(arr, arr+10); 
    std::cout << "0: " << *arr << std::endl; 
    std::cout << "Calling delete[], 20 dtors called" << std::endl; 
    delete[] arr; // 20 dtors called; 

    std::cout << "Calling malloc(sizeof*10), 0 ctors called" << std::endl;  
    arr = static_cast<Child*>(std::malloc(sizeof(Child)*10)); // no ctors 
    std::cout << "The next line will seg-fault" << std::endl; 
    // Segfault because the base pointers were never initialized 
    std::cout << "0: " << *arr << std::endl; // segfault 
    set(arr, 10); 
    print(arr, arr+10); 
    std::cout << "0: " << *arr << std::endl; 
    std::cout << "Calling free(), 0 dtors called" << std::endl; 
    free(arr); // no dtors 

    return 0; 
} 

Приведенный выше код соответствует и компилируется без ошибок на g++ и Visual Studio, но из-за наследства, как аварии, когда я пытаюсь напечатать первый элемент после malloc (так как базовый класс никогда не был инициализирован).

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

Надеюсь, что это поможет.