析构函数的使用=删除;
考虑以下课程:
struct S { ~S() = delete; };
不久,为了这个问题的目的:我不能像S s{};
那样创buildS s{};
实例S s{};
因为我不能摧毁他们。
正如在评论中提到的,我仍然可以通过执行S *s = new S;
来创build一个实例S *s = new S;
,但我不能删除它。
因此,我可以看到一个删除析构函数的唯一用法是这样的:
struct S { ~S() = delete; static void f() { } }; int main() { S::f(); }
也就是说,定义一个仅暴露一堆静态函数的类,并禁止任何尝试创build该类的实例。
删除析构函数的其他用途(如果有的话)是什么?
如果您有一个永远不会delete
的对象,或者将其存储在堆栈中(自动存储),或将其作为另一个对象的一部分存储,则=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
,它可能不是在堆栈中声明的,也不是dynamic分配的。 我们有一个系统来从一个已知的数组中获取它们。
我相信上面没有什么是不确定的行为; 不能销毁一个Bundle
是可以接受的,就像创build一个新的Bundle
一样。
而且界面不必暴露Bundle
工作方式。 只是一个不透明的Handle
。
现在,如果代码的其他部分需要知道所有句柄都在特定的缓冲区中,或者它们的生命周期是以特定的方式进行跟踪的,那么这种技术会很有用。 可能这也可以通过私人构造函数和朋友工厂函数来处理。
一种情况可能是防止错误的释放:
#include <stdlib.h> struct S { ~S() = delete; }; int main() { S* obj= (S*) malloc(sizeof(S)); // correct free(obj); // error delete obj; return 0; }
这是非常基本的,但适用于任何特殊的分配/释放过程(例如工厂)
一个更“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; };
有两个似是而非的用例。 首先(如一些注释),dynamic分配对象是可以接受的,不能delete
它们,并允许在程序结束时清理操作系统。
另外(甚至更奇怪)你可以分配一个缓冲区,并在其中创build一个对象,然后删除缓冲区来恢复的地方,但从来没有提示尝试调用析构函数。
#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; }
除了在特殊情况下,这些都不是好主意。 如果自动创build的析构函数什么也不做(因为所有成员的析构函数都是微不足道的),那么编译器将创build一个无效的析构函数。
如果自动创build的析构函数会做一些不重要的事情,那么很可能由于未能执行它的语义而损害程序的有效性。
让一个程序离开main()
并允许环境“清理”是一种有效的技术,但是最好避免,除非约束条件是绝对必要的。 充其量,这是一个掩盖真正的内存泄漏的好方法!
我怀疑这个function是否完整,能够delete
其他自动生成的成员。
我很想看到这个function的实际使用。
有一个静态类的概念(没有构造函数),所以逻辑上不需要析构函数。 但是这样的类更适当的实现,因为namespace
在现代C ++中没有(好的)地方,除非是模板化的。
为什么要将析构函数标记为delete
?
为了防止析构函数被调用,当然;)
什么是用例?
我可以看到至less 3种不同的用途:
- 该类不应该被实例化; 在这种情况下,我也希望删除默认的构造函数。
- 这个类的一个实例应该被泄露; 例如,一个日志logging单例实例
- 这个类的一个实例只能由一个特定的机制来创build和处理; 这在使用FFI时可能会发生
为了说明后一点,设想一个C接口:
struct Handle { /**/ }; Handle* xyz_create(); void xyz_dispose(Handle*);
在C ++中,你会想把它包装在一个unique_ptr
来自动发布,但是如果你不小心写了: unique_ptr<Handle>
? 这是一个运行时间的灾难!
相反,你可以调整类的定义:
struct Handle { /**/ ~Handle() = delete; };
然后编译器会扼杀unique_ptr<Handle>
强制您正确使用unique_ptr<Handle, xyz_dispose>
。
用new
创build一个对象的实例并且永远不删除它是实现一个C ++ Singleton的最安全的方法,因为它避免了任何和所有的销毁顺序问题。 这个问题的一个典型例子就是在另一个Singleton类的析构函数中访问的“Logging”单例。 Alexandrescu曾经在他的古典“现代C ++devise”一书中讨论了如何应对Singleton实现中的破坏顺序问题。
删除析构函数是很好的,所以即使是Singleton类本身也不能不小心删除实例。 它还可以防止像delete &SingletonClass::Instance()
这样的疯狂用法(如果Instance()
返回一个引用,就像它应该;没有理由返回一个指针)。
尽pipe如此,没有什么值得注意的。 当然,无论如何,你不应该首先使用单身。