Я только собираюсь обратиться ваш конкретный вопрос
Это можно удалить массив классов в 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-контейнер или типы интеллектуальных указателей).
Надеюсь, что это поможет.
'delete arr' - это неопределенное поведение; он может сделать что угодно. –
Почему вы не хотите называть деструкторов? Это, как правило, очень плохая идея. –
* Итак, мы пытаемся перехитрить C++ * - Почему вы хотите «перехитрить C++»?- * вы понимаете, что новые вставки перед указателем результата имеют значение unsigned long long, которое представляет количество выделенных T-экземпляров * - Это новости для меня. – PaulMcKenzie