首页 > 解决方案 > 为什么以及何时需要提供我自己的删除器?

问题描述

为什么以及何时需要提供我自己的删除器?关键字delete还不够吗?

如果您使用智能指针来管理分配的内存以外的资源new请记住传递一个删除器


更新:

正如评论中所问的那样,我不清楚引用的文本和示例的原因是我想错了一些事情,我一直认为智能指针只是为动态内存管理而发明的/与动态内存管理相关的。所以这个例子使用智能指针来管理一个非动态内存的东西让我很困惑。

一位前辈的解释很好:

智能指针根本不关心动态内存本身。这只是一种在您需要时跟踪某物的方法,并在超出范围时将其销毁。提到文件句柄、网络连接等,是为了指出它们不是动态内存,但智能指针无论如何都可以很好地管理它们。


C++ Primer 5th以伪网络连接(不定义析构函数)为例进行说明。

坏的:

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
}

完整的上下文截图(我清除了一些不相关的文本):
智能指针和哑类

智能指针和哑类第 2 部分

标签: c++smart-pointers

解决方案


delete当标准不适用于释放、释放、丢弃或以其他方式处置其生命周期由智能指针控制的资源时,您需要为智能指针创建提供自己的删除。

智能指针的典型用途是将内存分配为由智能指针管理的资源,以便当智能指针超出范围时,被管理的资源(在这种情况下为内存)通过使用delete运算符被丢弃。

标准delete操作符做了两件事:(1)调用对象的析构函数以允许对象在释放或释放分配的内存之前进行任何需要做的清理;(2)释放标准new操作符为构造时的对象。这与操作符发生的顺序相反,new操作符 (1) 为对象分配内存并执行为对象建立构造环境所需的基本初始化,以及 (2) 调用对象的构造函数来创建对象的起始状态. 请参阅C++ new 运算符除了分配和 ctor 调用还有什么作用?

所以需要你自己的删除器的关键问题是“在对象的析构函数完成后,在调用对象构造函数之前执行的哪些操作需要展开并退出?” .

通常这是某种类型的内存分配,例如由标准new运算符完成的。

但是,在使用运算符分配的内存以外的某些资源的情况下new,使用delete运算符是不合适的,因为该资源不是通过使用new运算符分配的内存。

因此,当在运算符不合适的情况下对此类资源使用智能指针时delete,您需要提供自己的删除器方法或函数或运算符,当智能指针超出范围并触发其自己的析构函数时,它将使用转而处理丢弃由智能指针管理的任何资源。

一个带输出的简单示例

我整理了一个简单的示例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.

附加职位

什么是智能指针,我应该什么时候使用它?

将自定义删除器与 std::shared_ptr 一起使用

另请参阅有关删除器的讨论std::make_shared<>以及为什么它不可用。如何将删除器传递给 make_shared?

std::unique_ptr 的自定义删除器是手动调用析构函数的有效位置吗?

如果 A 有析构函数,什么时候 std::unique_ptr<A> 需要一个特殊的删除器?

C++ 中的 RAII 和智能指针


推荐阅读