带有智能指针的非虚拟删除器

时间:2014-04-09 10:19:38

标签: c++ c++11 visual-studio-2013 smart-pointers unique-ptr

我正在阅读最新的Overload(link),并决定在第8页测试声明:

  

shared_ptr将在范围退出时正确调用B的析构函数   虽然A的析构函数不是虚拟的。

我正在使用Visual Studio 2013,编译器v120:

#include <memory>
#include <iostream>

struct A {
    ~A() { std::cout << "Deleting A"; }
};


struct B : public A
{
    ~B() { std::cout << "Deleting B"; }
};

int main()
{
    std::shared_ptr<A> ptr = std::make_shared<B>();
    ptr.reset();

    return 0;
}

这按预期工作并打印出“删除BDeleting A”

这篇文章似乎暗示这也适用于std :: unique_ptr:

  

几乎没有必要管理自己的资源,所以抵制   诱惑实现自己的副本/赋值/移动构造/移动   赋值/析构函数。

     

托管资源可以是内部资源   您的类定义或类本身的实例。   围绕标准容器和类模板重构代码   像unique_ptr或shared_ptr一样会让你的代码更具可读性   可维护性。

然而,改变时

    std::shared_ptr<A> ptr = std::make_shared<B>();

    std::unique_ptr<A> ptr = std::make_unique<B>();

程序只输出“删除A”

我是否误解了这篇文章并且该行为是否符合标准? 这是MSVC编译器的错误吗?

4 个答案:

答案 0 :(得分:2)

shared_ptrunique_ptr不同。 make_shared将在调用时创建一个类型擦除的删除对象,而使用unique_ptr时,删除器是该类型的一部分。因此,shared_ptr在调用删除器时知道实际类型,但unique_ptr没有。这使unique_ptr更有效率,这就是为什么以这种方式实现的。

我觉得这篇文章实际上有点误导。我不认为用虚函数公开基类的复制构造函数是个好建议,听起来像是很多切片问题。

考虑以下情况:

struct A{
    virtual void foo(){ std::cout << "base"; };
};
struct B : A{
    virtual void foo(){ std::cout << "derived"; };
};
void bar(A& a){
    a.foo(); //derived
    auto localA = a; //poor matanance porgrammer didn't notice that a is polymorphic
    localA.foo(); //base
}

我个人主张任何新的高层次的非侵入式多态性http://isocpp.org/blog/2012/12/value-semantics-and-concepts-based-polymorphism-sean-parent,它完全回避了这个问题。

答案 1 :(得分:0)

此行为符合标准。

与普通的new / delete相比,unique_ptr的设计性能没有。

shared_ptr允许有开销,所以它可以更聪明。

根据标准[20.7.1.2.2,20.7.2.2.2],unique_ptr调用get()返回的指针上的delete,而shared_ptr删除它所拥有的真实对象 - 它会记住正确的类型到删除(如果正确初始化),即使没有虚拟析构函数。

显然,shared_ptr并不是全知,你可以通过将指针传递给基础对象来欺骗它表现得很糟糕:

std::shared_ptr<Base> c = std::shared_ptr<Base> { (Base*) new Child() };

但是,无论如何这都是愚蠢的事情。

答案 2 :(得分:0)

用于做魔术的std::shared_ptr技术被称为type erasure。如果您使用的是gcc,请尝试查找文件bits/shared_ptr_base.h并检查实施情况。我使用的是gcc 4.7.2。

unique_ptr旨在实现最小开销,并且不使用类型擦除来记住它所持有的指针的实际类型。

以下是关于此主题的精彩讨论:link


编辑:shared_ptr的简单实现,以展示如何实现类型擦除。

#include <cstddef>

// A base class which does not know the type of the pointer tracking
class ref_counter_base
{
  public:
    ref_counter_base() : counter_(1) {}
    virtual ~ref_counter_base() {}

    void increase()
    {
      ++counter_;
    }
    void release() 
    {
      if (--counter_ == 0) {
        destroy();
        delete this;
      }
    }

    virtual void destroy() = 0;
  private:
    std::size_t counter_;
};

// The derived class that actually remembers the type of
// the pointer and deletes the pointer on destroy.
template <typename T>
class ref_counter : public ref_counter_base
{
  public:
    ref_counter(T *p) : p_(p) {}
    virtual void destroy() 
    {
      delete p_;
    }
  private:
    T *p_;
};

template <typename T>
class shared_ptr
{
  public:
    shared_ptr(T *p) 
      : p_(p)
      , counter_(new ref_counter<T>(p))
    {
    }

    // Y* should be implicitely convertable to T*,
    // i.e. Y is derived from T
    template <typename Y>
      shared_ptr(Y &other)
      : p_(other.get())
      , counter_(other.counter())
      {
        counter_->increase();
      }

    ~shared_ptr() 
    {
      counter_->release();
    }

    T* get() { return p_; }
    ref_counter_base* counter() { return counter_; }
  private:
    T *p_;
    ref_counter_base *counter_;
};

答案 3 :(得分:0)

可以执行与unique_ptr类似的操作,但由于其删除类型是静态确定的,因此您需要静态维护正确的删除器类型。即(Live demo at Coliru):

// Convert given pointer type to T and delete.
template <typename T>
struct deleter {
    template <typename U>
    void operator () (U* ptr) const {
        if (ptr) {
            delete static_cast<T*>(ptr);
        }
    }
};

// Create a unique_ptr with statically encoded deleter type.
template <typename T, typename...Args>
inline std::unique_ptr<T, deleter<T>>
make_unique_with_deleter(Args&&...args) {
    return std::unique_ptr<T, deleter<T>>{
        new T(std::forward<Args>(args)...)
    };
}

// Convert a unique_ptr with statically encoded deleter to
// a pointer to different type while maintaining the
// statically encoded deleter.
template <typename T, typename U, typename W>
inline std::unique_ptr<T, deleter<W>>
unique_with_deleter_cast(std::unique_ptr<U, deleter<W>> ptr) {
    T* t_ptr{ptr.release()};
    return std::unique_ptr<T, deleter<W>>{t_ptr};
}

// Create a unique_ptr to T with statically encoded
// deleter for type U.
template <typename T, typename U, typename...Args>
inline std::unique_ptr<T, deleter<U>>
make_unique_with_deleter(Args&&...args) {
    return unique_with_deleter_cast<T>(
        make_unique_with_deleter<U>(std::forward<Args>(args)...)
    );
}

使用起来有点尴尬:

std::unique_ptr<A, deleter<B>> foo = make_unique_with_deleter<A, B>();

auto改善了这种情况:

auto bar = make_unique_with_deleter<A, B>();

它并没有真正为你买单,因为动态类型是在unique_ptr的静态类型中编码的。如果您要携带动态类型,为什么不简单地使用unique_ptr<dynamic_type>?我猜想这样的东西可能在通用代码中有一些用处,但找到一个这样的例子留给读者练习。