2011-11-25 327 views
55

根据N3290 std::unique_ptr在其构造函数中接受deleter参数。那么,std :: unique_ptr的定制删除器如何工作?

但是,我不能在Windows中使用Visual C++ 10.0或MinGW g ++ 4.4.1,也不能在Ubuntu中使用g ++ 4.6.1。

因此,我害怕我对它的理解是不完整或错误的,我无法看到明显被忽略的删除参数,所以任何人都可以提供一个工作示例?

最好我想看看它是如何工作的unique_ptr<Base> p = unique_ptr<Derived>(new Derived)

可能使用标准中的一些措辞来备份示例,即使用您正在使用的任何编译器,它实际上做了它应该做的事情?

回答

45

这对我的作品在MSVC10

int x = 5; 
auto del = [](int * p) { std::cout << "Deleting x, value is : " << *p; }; 
std::unique_ptr<int, decltype(del)> px(&x, del); 

而且在GCC 4.5,here

我将跳过要标准,除非你不认为例子是做什么你” d期待它做到。

+0

gcc链接被破坏,有人可以重新编码吗? 'gcc'有什么区别? – alfC

+1

@alfC:没有区别。这与我的答案中显示的代码完全相同。链接是代码编译和运行的只是一个在线演示。我已经更新了它。 –

+2

为什么不只是“std :: unique_ptr px(&x);”? – Jon

6

This Works。破坏正常发生。

class Base 
{ 
    public: 
    Base() { std::cout << "Base::Base\n"; } 
    virtual ~Base() { std::cout << "Base::~Base\n"; } 
}; 


class Derived : public Base 
{ 
    public: 
    Derived() { std::cout << "Derived::Derived\n"; } 
    virtual ~Derived() { std::cout << "Derived::~Derived\n"; } 
}; 

void Delete(const Base* bp) 
{ 
    delete bp; 
} 

int main() 
{ 
    std::unique_ptr<Base, void(*)(const Base*)> ptr = std::unique_ptr<Derived, void(*)(const Base*)>(new Derived(), Delete); 
} 
10

我的问题已经很好地回答了。

但是为了以防万一人不知道,我有误解,认为一个unique_ptr<Derived>可以移动到unique_ptr<Base>,然后就记住了Derived对象缺失者,即是Base不会需要有一个虚析构函数。那是错误的。我选择Kerrek SB's comment作为“答案”,除了一个人不能这样评论。

@Howard:下面的代码说明了实现什么,我相信一个动态分配的缺失者的成本不得不意味着unique_ptr支持开箱即用的方式之一:

#include <iostream> 
#include <memory>   // std::unique_ptr 
#include <functional>  // function 
#include <utility>   // move 
#include <string> 
using namespace std; 

class Base 
{ 
public: 
    Base() { cout << "Base:<init>" << endl; } 
    ~Base() { cout << "Base::<destroy>" << endl; } 
    virtual string message() const { return "Message from Base!"; } 
}; 

class Derived 
    : public Base 
{ 
public: 
    Derived() { cout << "Derived::<init>" << endl; } 
    ~Derived() { cout << "Derived::<destroy>" << endl; } 
    virtual string message() const { return "Message from Derived!"; } 
}; 

class BoundDeleter 
{ 
private: 
    typedef void (*DeleteFunc)(void* p); 

    DeleteFunc deleteFunc_; 
    void*  pObject_; 

    template< class Type > 
    static void deleteFuncImpl(void* p) 
    { 
     delete static_cast< Type* >(p); 
    } 

public: 
    template< class Type > 
    BoundDeleter(Type* pObject) 
     : deleteFunc_(&deleteFuncImpl<Type>) 
     , pObject_(pObject) 
    {} 

    BoundDeleter(BoundDeleter&& other) 
     : deleteFunc_(move(other.deleteFunc_)) 
     , pObject_(move(other.pObject_)) 
    {} 

    void operator() (void*) const 
    { 
     deleteFunc_(pObject_); 
    } 
}; 

template< class Type > 
class SafeCleanupUniquePtr 
    : protected unique_ptr< Type, BoundDeleter > 
{ 
public: 
    typedef unique_ptr< Type, BoundDeleter > Base; 

    using Base::operator->; 
    using Base::operator*; 

    template< class ActualType > 
    SafeCleanupUniquePtr(ActualType* p) 
     : Base(p, BoundDeleter(p)) 
    {} 

    template< class Other > 
    SafeCleanupUniquePtr(SafeCleanupUniquePtr<Other>&& other) 
     : Base(move(other)) 
    {} 
}; 

int main() 
{ 
    SafeCleanupUniquePtr<Base> p(new Derived); 
    cout << p->message() << endl; 
} 

干杯,

+1

你能解释一下使用它必须具有一个非虚拟析构函数的类,但是从它派生并希望获得通过基指针调用的派生析构函数?这是否违背虚拟析构函数的常识? – stijn

+3

@stijn:只要其他一些机制(如自定义删除器)完成识别最派生类的工作,析构函数在技术上并不需要是虚拟的。然后使其成为非虚拟的一个有效原因是保持与外部施加的存储器布局的兼容性,即不希望在存储器区域的前面存在vtable ptr。另一个有效的原因是,如果销毁通常是通过删除器进行的,那么使析构器虚拟化会错误地指示可以使用C++'delete',或者至少有一些原因。 –

+0

@stjn另一个原因是你使用多种内存分配/释放技术,你只希望创建点知道该特定实例的分配策略/技术。因此跟踪相应正确的dtor和智能指针是非常有用的,这样与智能指针交互的其他代码点在编译时就不需要知道分配/解除分配策略。在这种情况下,它是一种信息隐藏和减少代码重复(DRY)的形式。 –

23

为了补充以前所有的答案,没有办法有一个定制删除,而无需通过具有或者函数指针或等价的东西在里面像这种“污染”了的unique_ptr签名:

std::unique_ptr< MyType, myTypeDeleter > // not pretty 

这是通过提供专业化的标准:: default_delete模板类,像这样实现的:

namespace std 
{ 
template<> 
class default_delete<MyType> 
{ 
public: 
    void operator()(MyType *ptr) 
    { 
    delete ptr; 
    } 
}; 
} 

现在所有std::unique_ptr<MyType>说,“看到”这种专业化将随之被删除。请注意,它可能不是您想要的所有std::unique_ptr<MyType>,所以请仔细选择您的解决方案。

+0

是不是在名称空间std坏习惯中编写代码? – odinthenerd

+1

专业std模板是合法的,不是一个坏习惯,但有一些“规则”,你需要遵循,请参阅[本文](http://stackoverflow.com/questions/8513417/what-c​​an-and-cant-i -specialize合的-STD-命名空间)。 std :: default_delete是模板专业化的完美人选。 –

+0

也许类似: 类BIGNUM_ptr:公共的std ::的unique_ptr { \t BIGNUM_ptr(BIGNUM B):标准::的unique_ptr (b,&:: BN_free); }; 虽然我还没有得到它的工作,然后我想要使这个类的模板... –

相关问题