析构函数的使用 = delete;
Uses of destructor = delete;
考虑以下 class:
struct S { ~S() = delete; };
很快,为了问题的目的:我无法创建像 S s{};
这样的 S
实例,因为我无法销毁它们。
正如评论中提到的,我仍然可以通过 S *s = new S;
创建一个实例,但我也不能删除它。
因此,对于已删除的析构函数,我能看到的唯一用途是这样的:
struct S {
~S() = delete;
static void f() { }
};
int main() {
S::f();
}
也就是说,定义一个只公开一堆静态函数的 class 并禁止任何创建该 class 实例的尝试。
删除的析构函数的其他用途(如果有的话)是什么?
一种情况可能是防止错误重新分配:
#include <stdlib.h>
struct S {
~S() = delete;
};
int main() {
S* obj= (S*) malloc(sizeof(S));
// correct
free(obj);
// error
delete obj;
return 0;
}
这是非常初级的,但适用于任何特殊的allocation/deallocation-process(例如工厂)
更多 'c++' 风格的例子
struct data {
//...
};
struct data_protected {
~data_protected() = delete;
data d;
};
struct data_factory {
~data_factory() {
for (data* d : data_container) {
// this is safe, because no one can call 'delete' on d
delete d;
}
}
data_protected* createData() {
data* d = new data();
data_container.push_back(d);
return (data_protected*)d;
}
std::vector<data*> data_container;
};
有两个可能的用例。首先(正如一些评论所指出的)动态分配对象,无法delete
它们并允许操作系统在程序结束时清理是可以接受的。
或者(甚至更奇怪)您可以分配一个缓冲区并在其中创建一个对象,然后删除缓冲区以恢复该位置但永远不会提示尝试调用析构函数。
#include <iostream>
struct S {
const char* mx;
const char* getx(){return mx;}
S(const char* px) : mx(px) {}
~S() = delete;
};
int main() {
char *buffer=new char[sizeof(S)];
S *s=new(buffer) S("not deleting this...");//Constructs an object of type S in the buffer.
//Code that uses s...
std::cout<<s->getx()<<std::endl;
delete[] buffer;//release memory without requiring destructor call...
return 0;
}
None 这些似乎是个好主意,除非在特殊情况下。如果自动创建的析构函数什么都不做(因为所有成员的析构函数都是微不足道的),那么编译器将创建一个无效的析构函数。
如果自动创建的析构函数会做一些不平凡的事情,您很可能会因未能执行其语义而损害程序的有效性。
让程序离开 main()
并让环境进入 'clean-up' 是一种有效的技术,但最好避免,除非限制使它绝对必要。充其量这是掩盖真正内存泄漏的好方法!
我怀疑该功能的完整性是为了 delete
其他自动生成的成员。
我很想看到这个功能的实际应用。
有静态 class 的概念(没有构造函数),因此逻辑上不需要析构函数。但是这样的 classes 更合适地实现为 namespace
在现代 C++ 中没有(好的)位置,除非模板化。
如果你有一个永远不应该 delete
d 或存储在堆栈中(自动存储)或作为另一个对象的一部分存储的对象,=delete
将阻止所有这些.
struct Handle {
~Handle()=delete;
};
struct Data {
std::array<char,1024> buffer;
};
struct Bundle: Handle {
Data data;
};
using bundle_storage = std::aligned_storage_t<sizeof(Bundle), alignof(Bundle)>;
std::size_t bundle_count = 0;
std::array< bundle_storage, 1000 > global_bundles;
Handle* get_bundle() {
return new ((void*)global_bundles[bundle_count++]) Bundle();
}
void return_bundle( Handle* h ) {
Assert( h == (void*)global_bundles[bundle_count-1] );
--bundle_count;
}
char get_char( Handle const* h, std::size_t i ) {
return static_cast<Bundle*>(h).data[i];
}
void set_char( Handle const* h, std::size_t i, char c ) {
static_cast<Bundle*>(h).data[i] = c;
}
这里我们有不透明的 Handle
s,它们可能不会在堆栈上声明,也不会动态分配。我们有一个系统可以从已知数组中获取它们。
我相信以上都不是未定义的行为;未能销毁 Bundle
是可以接受的,创建一个新的取而代之也是如此。
而且界面不必公开 Bundle
的工作原理。只是一个不透明的 Handle
.
现在,如果代码的其他部分需要知道所有句柄都在该特定缓冲区中,或者以特定方式跟踪它们的生命周期,则此技术会很有用。可能这也可以用私有构造函数和友元工厂函数来处理。
为什么要将析构函数标记为delete
?
当然是为了防止析构函数被调用;)
有哪些用例?
我可以看到至少 3 种不同的用途:
- 永远不要实例化 class;在这种情况下,我还希望删除默认构造函数。
- 这个 class 的一个实例应该被泄露;例如,一个日志单例实例
- 此 class 的实例只能通过特定机制创建和处理;这在使用 FFI
时尤其会发生
为了说明后一点,想象一个 C 接口:
struct Handle { /**/ };
Handle* xyz_create();
void xyz_dispose(Handle*);
在 C++ 中,您可能希望将其包装在 unique_ptr
中以自动发布,但是如果您不小心写了:unique_ptr<Handle>
怎么办?这是运行时代的灾难!
因此,您可以调整 class 定义:
struct Handle { /**/ ~Handle() = delete; };
然后编译器会阻塞 unique_ptr<Handle>
迫使你正确地使用 unique_ptr<Handle, xyz_dispose>
。
使用 new
创建一个对象的实例并且从不删除它是实现 C++ 单例的最安全的方法,因为它避免了任何和所有的销毁顺序问题。此问题的典型示例是 "Logging" 单例,它在另一个单例 class 的析构函数中被访问。 Alexandrescu 曾在他的 classical "Modern C++ Design" 书中用一整节的篇幅介绍如何处理单例实现中的销毁顺序问题。
删除的析构函数很好,这样即使是单例class本身也不会意外删除实例。它还可以防止像 delete &SingletonClass::Instance()
这样的疯狂使用(如果 Instance()
return 是一个引用,它应该是;没有理由让它 return 一个指针)。
不过,归根结底,这并没有什么值得注意的。当然,你一开始就不应该使用 Singletons。
考虑以下 class:
struct S { ~S() = delete; };
很快,为了问题的目的:我无法创建像 S s{};
这样的 S
实例,因为我无法销毁它们。
正如评论中提到的,我仍然可以通过 S *s = new S;
创建一个实例,但我也不能删除它。
因此,对于已删除的析构函数,我能看到的唯一用途是这样的:
struct S {
~S() = delete;
static void f() { }
};
int main() {
S::f();
}
也就是说,定义一个只公开一堆静态函数的 class 并禁止任何创建该 class 实例的尝试。
删除的析构函数的其他用途(如果有的话)是什么?
一种情况可能是防止错误重新分配:
#include <stdlib.h>
struct S {
~S() = delete;
};
int main() {
S* obj= (S*) malloc(sizeof(S));
// correct
free(obj);
// error
delete obj;
return 0;
}
这是非常初级的,但适用于任何特殊的allocation/deallocation-process(例如工厂)
更多 'c++' 风格的例子
struct data {
//...
};
struct data_protected {
~data_protected() = delete;
data d;
};
struct data_factory {
~data_factory() {
for (data* d : data_container) {
// this is safe, because no one can call 'delete' on d
delete d;
}
}
data_protected* createData() {
data* d = new data();
data_container.push_back(d);
return (data_protected*)d;
}
std::vector<data*> data_container;
};
有两个可能的用例。首先(正如一些评论所指出的)动态分配对象,无法delete
它们并允许操作系统在程序结束时清理是可以接受的。
或者(甚至更奇怪)您可以分配一个缓冲区并在其中创建一个对象,然后删除缓冲区以恢复该位置但永远不会提示尝试调用析构函数。
#include <iostream>
struct S {
const char* mx;
const char* getx(){return mx;}
S(const char* px) : mx(px) {}
~S() = delete;
};
int main() {
char *buffer=new char[sizeof(S)];
S *s=new(buffer) S("not deleting this...");//Constructs an object of type S in the buffer.
//Code that uses s...
std::cout<<s->getx()<<std::endl;
delete[] buffer;//release memory without requiring destructor call...
return 0;
}
None 这些似乎是个好主意,除非在特殊情况下。如果自动创建的析构函数什么都不做(因为所有成员的析构函数都是微不足道的),那么编译器将创建一个无效的析构函数。
如果自动创建的析构函数会做一些不平凡的事情,您很可能会因未能执行其语义而损害程序的有效性。
让程序离开 main()
并让环境进入 'clean-up' 是一种有效的技术,但最好避免,除非限制使它绝对必要。充其量这是掩盖真正内存泄漏的好方法!
我怀疑该功能的完整性是为了 delete
其他自动生成的成员。
我很想看到这个功能的实际应用。
有静态 class 的概念(没有构造函数),因此逻辑上不需要析构函数。但是这样的 classes 更合适地实现为 namespace
在现代 C++ 中没有(好的)位置,除非模板化。
如果你有一个永远不应该 delete
d 或存储在堆栈中(自动存储)或作为另一个对象的一部分存储的对象,=delete
将阻止所有这些.
struct Handle {
~Handle()=delete;
};
struct Data {
std::array<char,1024> buffer;
};
struct Bundle: Handle {
Data data;
};
using bundle_storage = std::aligned_storage_t<sizeof(Bundle), alignof(Bundle)>;
std::size_t bundle_count = 0;
std::array< bundle_storage, 1000 > global_bundles;
Handle* get_bundle() {
return new ((void*)global_bundles[bundle_count++]) Bundle();
}
void return_bundle( Handle* h ) {
Assert( h == (void*)global_bundles[bundle_count-1] );
--bundle_count;
}
char get_char( Handle const* h, std::size_t i ) {
return static_cast<Bundle*>(h).data[i];
}
void set_char( Handle const* h, std::size_t i, char c ) {
static_cast<Bundle*>(h).data[i] = c;
}
这里我们有不透明的 Handle
s,它们可能不会在堆栈上声明,也不会动态分配。我们有一个系统可以从已知数组中获取它们。
我相信以上都不是未定义的行为;未能销毁 Bundle
是可以接受的,创建一个新的取而代之也是如此。
而且界面不必公开 Bundle
的工作原理。只是一个不透明的 Handle
.
现在,如果代码的其他部分需要知道所有句柄都在该特定缓冲区中,或者以特定方式跟踪它们的生命周期,则此技术会很有用。可能这也可以用私有构造函数和友元工厂函数来处理。
为什么要将析构函数标记为delete
?
当然是为了防止析构函数被调用;)
有哪些用例?
我可以看到至少 3 种不同的用途:
- 永远不要实例化 class;在这种情况下,我还希望删除默认构造函数。
- 这个 class 的一个实例应该被泄露;例如,一个日志单例实例
- 此 class 的实例只能通过特定机制创建和处理;这在使用 FFI 时尤其会发生
为了说明后一点,想象一个 C 接口:
struct Handle { /**/ };
Handle* xyz_create();
void xyz_dispose(Handle*);
在 C++ 中,您可能希望将其包装在 unique_ptr
中以自动发布,但是如果您不小心写了:unique_ptr<Handle>
怎么办?这是运行时代的灾难!
因此,您可以调整 class 定义:
struct Handle { /**/ ~Handle() = delete; };
然后编译器会阻塞 unique_ptr<Handle>
迫使你正确地使用 unique_ptr<Handle, xyz_dispose>
。
使用 new
创建一个对象的实例并且从不删除它是实现 C++ 单例的最安全的方法,因为它避免了任何和所有的销毁顺序问题。此问题的典型示例是 "Logging" 单例,它在另一个单例 class 的析构函数中被访问。 Alexandrescu 曾在他的 classical "Modern C++ Design" 书中用一整节的篇幅介绍如何处理单例实现中的销毁顺序问题。
删除的析构函数很好,这样即使是单例class本身也不会意外删除实例。它还可以防止像 delete &SingletonClass::Instance()
这样的疯狂使用(如果 Instance()
return 是一个引用,它应该是;没有理由让它 return 一个指针)。
不过,归根结底,这并没有什么值得注意的。当然,你一开始就不应该使用 Singletons。