析构函数的使用=删除;

sky*_*ack 46 c++ destructor c++11

考虑以下课程:

struct S { ~S() = delete; };
Run Code Online (Sandbox Code Playgroud)

很快就问题的目的:我无法创建S类似的实例,S s{};因为我无法销毁它们.
正如评论中所提到的,我仍然可以创建一个实例S *s = new S;,但我也无法删除它.
因此,我可以看到删除析构函数的唯一用法是这样的:

struct S {
    ~S() = delete;
    static void f() { }
};

int main() {
    S::f();
}
Run Code Online (Sandbox Code Playgroud)

也就是说,定义一个只暴露一堆静态函数的类,并禁止任何创建该类实例的尝试.

删除的析构函数的其他用途(如果有的话)是什么?

Yak*_*ont 22

如果你有一个永远不应该存在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;
}
Run Code Online (Sandbox Code Playgroud)

这里我们有opaque Handles,它们可能不会在堆栈上声明,也不会动态分配.我们有一个从已知阵列中获取它们的系统.

我相信上面没有任何不确定的行为; 未能摧毁a Bundle是可以接受的,就像在它的位置创建一个新的那样.

并且界面不必暴露如何Bundle工作.只是一个不透明的Handle.

现在,如果代码的其他部分需要知道所有句柄都在特定缓冲区中,或者以特定方式跟踪它们的生命周期,则此技术非常有用.可能这也可以通过私有构造函数和朋友工厂函数来处理.

  • 我认为,如果不强制执行工厂(如非公共ctor的情况那样),自动(并且,我认为,静态)存储持续时间是不可能的. (2认同)
  • `~Handle=delete();` 肯定应该是 `~Handle()=delete;` 吗? (2认同)

Dom*_*mso 16

一种情况可能是防止错误的释放:

#include <stdlib.h>

struct S {
    ~S() = delete;
};


int main() {

    S* obj= (S*) malloc(sizeof(S));

    // correct
    free(obj);

    // error
    delete obj;

    return 0;

}
Run Code Online (Sandbox Code Playgroud)

这是非常基本的,但适用于任何特殊的分配/解除分配过程(例如工厂)

一个更'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;
};
Run Code Online (Sandbox Code Playgroud)

  • 称这种方法"正确"有点奇怪.你通常会尝试在C++中避免使用`malloc`和`free`. (8认同)
  • 我想这里的意图是,使用一些自定义的分配和解除分配(工厂)的方式而不是`new`和`delete`.对于像'my_alloc`和`my_dealloc`这样的名字,可能会更清楚; 名称`malloc`和`free`只是一个非常糟糕的例子.另外,`my_alloc`应该返回一个正确的指针(不是`void*`). (3认同)

Mat*_* M. 9

为什么要将析构函数标记为delete

当然,为了防止析构函数被调用;)

有什么用例?

我可以看到至少 3种不同的用途:

  1. 该类永远不应该被实例化; 在这种情况下,我还希望删除默认构造函数.
  2. 应该泄露这个类的一个实例; 例如,记录单例实例
  3. 只能通过特定机制创建和处理此类的实例; 使用FFI时可能会出现这种情况

为了说明后一点,设想一个C接口:

struct Handle { /**/ };

Handle* xyz_create();
void xyz_dispose(Handle*);
Run Code Online (Sandbox Code Playgroud)

在C++中,您可能希望将其包装在一个unique_ptr自动化版本中,但如果您不小心写了unique_ptr<Handle>怎么办?这是一场运行时的灾难!

因此,您可以调整类定义:

struct Handle { /**/ ~Handle() = delete; };
Run Code Online (Sandbox Code Playgroud)

然后编译器将阻止unique_ptr<Handle>强制您正确使用unique_ptr<Handle, xyz_dispose>.

  • @wasthishelpful:不; 不像`shared_ptr`类型 - 擦除类型的析构函数,`unique_ptr`没有间接.另外,请注意`xyz_create`是一个C函数,它返回一个裸骨`Handle*`,而不是`unique_ptr`.最后,如果试图将`unique_ptr <Handle,xyz_dispose>`转换为`unique_ptr <Handle>`,我会发现编译错误. (3认同)
  • 如果您正在调整定义(可能在“#ifdef”中),为什么不编写“~Handle() {xyz_dispose(this);}”而不是删除析构函数?现在,“unique_ptr&lt;Handle&gt;”会自动执行明智的操作,而不是让您将处理程序写入该类型的每次使用中(可以“typedef”,但仍然......) (2认同)

Per*_*xty 7

有两个看似合理的用例.首先(正如一些评论所述),动态分配对象,对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;
}
Run Code Online (Sandbox Code Playgroud)

除了在专业情况下,这些似乎都不是一个好主意.如果自动创建的析构函数不执行任何操作(因为所有成员的析构函数都是微不足道的),那么编译器将创建一个无效的析构函数.

如果自动创建的析构函数会做一些非常重要的事情,那么很可能会因为未能执行其语义而损害程序的有效性.

让程序离开main()并允许环境"清理"是一种有效的技术,但最好避免,除非限制使其成为绝对必要的.充其量它是掩盖真正的内存泄漏的好方法!

我怀疑该功能是否完整,具有delete其他自动生成成员的能力.

我很想看到这种能力的实际用途.

有一个静态类(没有构造函数)的概念,因此逻辑上不需要析构函数.但是这些类更适合实现为namespace现代C++中没有(好)的地方,除非模板化.

  • 具有静态方法的类可以模板化,命名空间不能.特别是特质班; 你是说allocator_traits是坏的现代c ++?我认为你的最后一段夸大了它的情况. (3认同)
  • "特征示例是一个很好的观点.我仍然认为你想要一个带有函数模板集合的命名空间"我认为在大多数包含静态函数的特征情况下,如果你试图用函数模板的命名空间替换特征,它在实践中会很糟糕.因为,类模板可以是部分专用的,而功能模板则不能.它们可以超载,但规则是不同的.此外,它通常会导致更糟糕的错误消息.我同意Nir Friedman关于你的最后一段,这两件事听起来很相似,但实际上并不相同. (2认同)

Chr*_*ckl 5

创建对象的实例new并从不删除它是实现C++ Singleton最安全的方法,因为它避免了任何和所有破坏顺序问题.这个问题的一个典型例子是"Logging"Singleton,它是在另一个Singleton类的析构函数中访问的.Alexandrescu曾经在他的经典"现代C++设计"一书中专门讨论了如何应对Singleton实现中的破坏顺序问题.

删除的析构函数很好,即使Singleton类本身也不会意外删除该实例.它还可以防止疯狂使用delete &SingletonClass::Instance()(如果Instance()返回引用,则应该;它没有理由返回指针).

但最终,这一点并不值得注意.当然,你不应该首先使用Singletons.