在不调用析构函数的情况下删除 类 的数组
Delete array of classes without calling destructors
考虑我们使用这种方式创建数组:
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++ 中删除 类 的数组而不调用它们的析构函数?
也许你想要::operator delete[](arr)
。
(见http://en.cppreference.com/w/cpp/memory/new/operator_delete)
但这仍然有未定义的行为,是个糟糕的主意。
一种无需调用析构函数即可解除分配的简单方法是将分配和初始化分开。当您适当注意对齐时,您可以使用 placement new
(或标准分配器对象的功能)在分配的块内创建对象实例。然后最后你可以使用适当的释放函数释放块。
我想不出在任何情况下这样做都是明智的:它有强烈的过早优化和 X/Y-problem(通过想象不切实际的 Y 作为解决方案来处理问题 X,然后询问仅约 Y)。
A new
-expression 旨在将分配与初始化结合起来,以便它们作为全有或全无操作执行。这种耦合,以及用于清理和释放的耦合,是正确性的关键,它也简化了很多事情(即,内部有一个人不必处理的复杂性)。脱钩需要有一个很好的理由。避免析构函数调用,例如优化的目的,不是一个很好的理由。
我只会回答你的具体问题
Is that possible to delete an array of classes in C++ without calling their destructors?
简短的回答是是。
长答案是肯定的,但有一些注意事项,具体考虑什么是析构函数 for(即资源清理),通常避免调用 class析构函数。
在我继续回答之前,应该注意的是,这是专门回答你的问题,如果你使用的是 C++(相对于直接 C),使用这段代码是可行的(因为它是兼容的),但是如果您需要以这种方式生成代码,您可能需要重新考虑您的某些设计,因为如果使用不当,这样的代码可能会导致 bugs/errors 和一般的未定义行为。
TL;DR 如果你需要避免析构函数,你需要重新考虑你的设计(即使用 copy/move 语义或 STL 容器)。
可以使用malloc
and 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
and/ormalloc
与delete
混合会导致未定义的行为,因此调用 MyClass* arr = new MyClass[10];
然后调用 free(arr);
可能无法像 "expected" (因此是 UB)那样工作。
在 C++ 中不调用 constructor/destructor 会引起的另一个问题是继承。上面的代码将与 malloc
和 free
一起用于基本的 classes,但是如果你开始抛出更复杂的类型,或者从其他 classes 继承,constructors/destructors 继承的 classes 不会被调用,事情很快就会变得丑陋,例如:
#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
之后的第一个元素时,两者都崩溃了(因为基础 class 从未被初始化)。
因此您确实可以创建和删除对象数组而无需调用它们的构造函数和析构函数,但这样做会导致一系列额外的场景,您需要注意并考虑以避免未定义的行为或崩溃,并且如果您的代码属于这种情况,您需要确保不调用析构函数,您可能需要重新考虑您的整体设计(甚至可能使用 STL 容器或智能指针类型)。
希望能帮到你。
考虑我们使用这种方式创建数组:
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++ 中删除 类 的数组而不调用它们的析构函数?
也许你想要::operator delete[](arr)
。
(见http://en.cppreference.com/w/cpp/memory/new/operator_delete)
但这仍然有未定义的行为,是个糟糕的主意。
一种无需调用析构函数即可解除分配的简单方法是将分配和初始化分开。当您适当注意对齐时,您可以使用 placement new
(或标准分配器对象的功能)在分配的块内创建对象实例。然后最后你可以使用适当的释放函数释放块。
我想不出在任何情况下这样做都是明智的:它有强烈的过早优化和 X/Y-problem(通过想象不切实际的 Y 作为解决方案来处理问题 X,然后询问仅约 Y)。
A new
-expression 旨在将分配与初始化结合起来,以便它们作为全有或全无操作执行。这种耦合,以及用于清理和释放的耦合,是正确性的关键,它也简化了很多事情(即,内部有一个人不必处理的复杂性)。脱钩需要有一个很好的理由。避免析构函数调用,例如优化的目的,不是一个很好的理由。
我只会回答你的具体问题
Is that possible to delete an array of classes in C++ without calling their destructors?
简短的回答是是。
长答案是肯定的,但有一些注意事项,具体考虑什么是析构函数 for(即资源清理),通常避免调用 class析构函数。
在我继续回答之前,应该注意的是,这是专门回答你的问题,如果你使用的是 C++(相对于直接 C),使用这段代码是可行的(因为它是兼容的),但是如果您需要以这种方式生成代码,您可能需要重新考虑您的某些设计,因为如果使用不当,这样的代码可能会导致 bugs/errors 和一般的未定义行为。
TL;DR 如果你需要避免析构函数,你需要重新考虑你的设计(即使用 copy/move 语义或 STL 容器)。
可以使用malloc
and 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
and/ormalloc
与delete
混合会导致未定义的行为,因此调用 MyClass* arr = new MyClass[10];
然后调用 free(arr);
可能无法像 "expected" (因此是 UB)那样工作。
在 C++ 中不调用 constructor/destructor 会引起的另一个问题是继承。上面的代码将与 malloc
和 free
一起用于基本的 classes,但是如果你开始抛出更复杂的类型,或者从其他 classes 继承,constructors/destructors 继承的 classes 不会被调用,事情很快就会变得丑陋,例如:
#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
之后的第一个元素时,两者都崩溃了(因为基础 class 从未被初始化)。
因此您确实可以创建和删除对象数组而无需调用它们的构造函数和析构函数,但这样做会导致一系列额外的场景,您需要注意并考虑以避免未定义的行为或崩溃,并且如果您的代码属于这种情况,您需要确保不调用析构函数,您可能需要重新考虑您的整体设计(甚至可能使用 STL 容器或智能指针类型)。
希望能帮到你。