代码之家  ›  专栏  ›  技术社区  ›  Rick Jim DeLaHunt

为什么以及何时需要提供自己的删除程序?

  •  15
  • Rick Jim DeLaHunt  · 技术社区  · 7 年前

    为什么和什么时候我需要 提供我自己的删除程序 ?不是关键字 delete 够了吗?

    如果使用智能指针管理内存以外的资源 分配人 new , 记住传递一个删除程序 .


    更新:

    正如在评论中被问到的,我之所以对引用的文本和示例不清楚,是因为我对某些事情的想法是错误的,这是因为我一直在考虑智能指针,它只是为动态内存管理而发明的/与之相关的。所以这个例子使用智能指针来管理非动态的内存,这让我很困惑。

    一个高年级学生的好解释:

    智能指针根本不关心动态的东西 像这样的记忆。这只是一种在你 需要它,并且当它超出范围时销毁它。这个 提到文件句柄、网络连接等的要点是 指出它们不是动态内存,但智能指针可以 不管怎样,管理他们都很好。


    C++引物第五 以一个伪网络连接(不定义析构函数)为例。

    坏的:

    struct destination; // represents what we are connecting to
    struct connection; // information needed to use the connection
    connection connect(destination*); // open the connection
    void disconnect(connection); // close the given connection
    void f(destination &d /* other parameters */)
    {
    // get a connection; must remember to close it when done
    connection c = connect(&d);
    // use the connection
    // if we forget to call disconnect before exiting f, there will be no way to closes
    }
    

    好:

    void end_connection(connection *p) { disconnect(*p); }
    void f(destination &d /* other parameters */)
    {
    connection c = connect(&d);
    shared_ptr<connection> p(&c, end_connection);
    // use the connection
    // when f exits, even if by an exception, the connection will be properly closed
    }
    

    完整上下文屏幕截图(我清除一些不相关的文本):
    Smart Pointers and Dumb classes

    Smart Pointers and Dumb classes part 2

    4 回复  |  直到 7 年前
        1
  •  8
  •   Richard Chambers    7 年前

    当标准 delete 不适用于解除分配、释放、丢弃或以其他方式处置其生命周期由智能指针控制的资源。

    智能指针的典型用途是将内存分配为由智能指针管理的资源,以便当智能指针超出范围时,在本例中,通过使用 删除 操作员。

    标准 删除 运算符做两件事:(1)调用对象的析构函数,允许对象在释放或释放分配的内存之前执行所需的任何清理;(2)释放由标准分配的内存。 new 对象构造时的运算符。这与 新的 运算符,它(1)为对象分配内存,并执行为对象建立构造环境所需的基本初始化;(2)调用对象的构造函数以创建对象的启动状态。见 What does the C++ new operator do other than allocation and a ctor call?

    所以需要自己删除的关键问题是 “调用对象构造函数之前所做的哪些操作需要在对象的析构函数完成后解除绑定并退出?” .

    通常情况下,这是一种内存分配,如标准配置 新的 操作员。

    但是,在某些资源(内存除外)的情况下, 新的 操作员,使用 删除 运算符不合适,因为资源不是使用 新的 操作员。

    因此,当对此类资源使用智能指针时, 删除 运算符不合适,您需要提供自己的deleter方法、函数或运算符,智能指针超出范围时将使用该方法、函数或运算符,并触发自己的析构函数,而后者将处理智能指针管理的任何资源的丢弃。

    一个简单的输出示例

    我用 std::unique_ptr<> 与生成的输出一起显示使用和不使用带有指针的删除程序,以及显式使用析构函数。

    简单Windows控制台应用程序的源代码如下:

    // ConsoleSmartPointer.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    
    #include <memory>
    #include <string>
    #include <iostream>
    
    class Fred {
    public:
        Fred() { std::cout << "  Fred Constructor called." << std::endl; }
        ~Fred() { std::cout << "  Fred Destructor called." << std::endl; }
    };
    class George {
    public:
        George() { std::cout << "   George Constructor called" << std::endl; }
        ~George() { std::cout << "   George Destructor called" << std::endl; }
    private:
        int iSomeData;
        std::string  a_label;
        Fred  myFred;
    };
    
    void cleanupGeorge(George *)
    {
        // just write out a log and do not explicitly call the object destructor.
        std::cout << "  cleanupGeorge() called" << std::endl;
    }
    
    void cleanupGeorge2(George *x)
    {
        // write out our message and then explicitly call the destructor for our
        // object that we are the deleter for.
        std::cout << "  cleanupGeorge2() called" << std::endl;
        x->~George();    // explicitly call destructor to do cleanup.
    }
    
    int func1()
    {
        // create a unique_ptr<> that does not have a deleter.
        std::cout << "func1 start. No deleter." << std::endl;
    
        std::unique_ptr<George> p(new George);
    
        std::cout << "func1 end." << std::endl;
        return 0;
    }
    
    int func2()
    {
        // create a unique_ptr<> with a deleter that will not explicitly call the destructor of the
        // object created.
        std::cout << "func2 start. Special deleter, no explicit destructor call." << std::endl;
    
        std::unique_ptr<George, void(*)(George *)> p(new George, cleanupGeorge);
    
        std::cout << "func2 end." << std::endl;
        return 0;
    }
    
    int func3()
    {
        // create a unique_ptr<> with a deleter that will trigger the destructor of the
        // object created.
        std::cout << "func3 start. Special deleter, explicit destructor call in deleter." << std::endl;
    
        std::unique_ptr<George, void(*)(George *)> p(new George, cleanupGeorge2);
    
        std::cout << "func3 end." << std::endl;
        return 0;
    }
    
    int main()
    {
        func1();
        func2();
        func3();
        return 0;
    }
    

    上述简单应用程序生成以下输出:

    func1 start. No deleter.
      Fred Constructor called.
       George Constructor called
    func1 end.
       George Destructor called
      Fred Destructor called.
    func2 start. Special deleter, no explicit destructor call.
      Fred Constructor called.
       George Constructor called
    func2 end.
      cleanupGeorge() called
    func3 start. Special deleter, explicit destructor call in deleter.
      Fred Constructor called.
       George Constructor called
    func3 end.
      cleanupGeorge2() called
       George Destructor called
      Fred Destructor called.
    

    附加职位

    What is a smart pointer and when should I use one?

    Using custom deleter with std::shared_ptr

    另请参阅有关Deleter的讨论 std::make_shared<> 以及为什么它不可用。 How to pass deleter to make_shared?

    Is custom deleter for std::unique_ptr a valid place for manual call to destructor?

    When does std::unique_ptr<A> need a special deleter if A has a destructor?

    RAII and smart pointers in C++

        2
  •  5
  •   Smi    7 年前

    什么时候(显然) delete 不是您想要销毁对象的方式。分配给的对象 placement new 可能是一个简单的例子。

    引言中的例子实际上相当好(我欠他们一次之后 trashing them earlier 但是另一种创造性的使用 std::shared_ptr (或) std::unique_ptr )可能是管理COM对象的生存期。通过调用他们的 Release () 方法,而不是通过调用 删除 (如果你这样做了,那么,维也纳晚安)。

    所以,为了说明这一点,您可以这样做:

    static void release_com_object (IUnknown *obj) { obj->Release (); }
    
    IUnknown *my_com_object = ...
    std::shared_ptr <IUnknown> managed_com_object (my_com_object, release_com_object);
    

    你不需要了解任何关于COM的知识就可以理解这里的基本思想。一般来说,释放资源的方法有很多种,合适的自定义删除程序集可以处理所有这些资源,这是一个非常酷的技巧。


    啊,我现在真的进入了最佳状态。这是给你的另一个,这次 STD:UngQuyPPTR 还有一个lambda(不知道他们为什么用 shared_ptr 在那儿——要贵得多)。使用时请注意不同的语法 STD:UngQuyPPTR -您必须告诉模板删除程序的函数签名:

    FILE *f = fopen ("myfile", "r");
    
    if (f)
    {
        std::unique_ptr <FILE, void (*) (FILE *)> (f, [] (FILE *f) { fclose (f); });
        // Do stuff with f
    }   // file will be closed here
    

    哦,天哪,你能做的太多了。

    Live demo .

        3
  •  3
  •   lubgr    7 年前

    该示例演示如何利用类型实例的确定性生存期。销毁后发生的事情由析构函数定义(排除内置类型,它们没有内置类型)。析构函数是“清除”其状态的类型的一部分。虽然通常没有太多的工作要做,但是内存分配确实需要清理,在本例中,必须调用一个断开连接函数。这对于手动管理资源的任何类型都是正确的(成员变量的简单聚合或aquantance除外),并且该示例同样可以

    class ConnectionHandle {
        public:
            ConnectionHandle(destination& d) : c(connect(d)) {}
            ~ConnectionHandle() { end_connection(c); }
        private:
            connection& c;
    };
    

    当这种类型的生存期由智能指针管理时,使用智能指针的析构函数来清理资源是一种可能,这就是exapmle的目的。这对 std::shared_ptr 以及 std::unique_ptr 但在后一种情况下,自定义删除程序是类型签名的一部分(传递 unique_ptr 周围)。

    将这种情况与那些 自定义删除程序是必需的:

    struct A { int i; std::string str; };
    
    auto sp = std::make_shared<A>(42, "foo");
    

    这里的资源 A 价值观是否属于 (“聚合”),清除将自动进行(无需执行任何操作 i , str 由管理 std::string::~string() )

        4
  •  2
  •   Robert Rouhani    7 年前

    C++允许您编写自己的自定义分配器。 new . 就像你应该怎么做 delete 你的一切 新的 ,您应该让自定义分配器分配的所有内容也被它删除。

    如果使用自定义分配程序来跟踪内存预算(即,将每个分配分配分配给某个预算,并在超出任何预算时发出警告),则会出现由这一点引起的问题的具体示例。假设这个包裹 新的 删除 ,所以当智能指针超出范围时,只有 删除 被调用,自定义分配器不知道内存被释放,最终导致预算内存使用不准确。

    如果使用相同类型的包装分配器检测泄漏,则调用 删除 直接导致假阳性。

    如果你真的因为任何原因手动分配你自己的内存,那么当你 删除 试图释放它。

    在您的示例中,网络连接的内存将被释放,而不会首先被完全断开连接。在实际情况下,这样做的结果可能是连接的另一端挂起,直到超时为止,或者出现关于断开连接的某种错误。

    推荐文章