从工厂函数返回std :: unique_ptr <t>,创建纯虚拟接口

时间:2015-07-08 18:13:14

标签: c++ smart-pointers c++14

我正在阅读提升文档中提供的Smart Pointer Programming Techniques

在“using abstract classes for implementation hiding”一节中,它们提供了一个很好的习惯用法来完全隐藏纯虚拟接口背后的实现。例如:

// Foo.hpp

#include <memory>

class Foo {
 public:
  virtual void Execute() const = 0;
 protected:
  ~Foo() = default;
};

std::shared_ptr<const Foo> MakeFoo();

// Foo.cpp

#include "Foo.hpp"
#include <iostream>

class FooImp final
    : public Foo {
public:
  FooImp()                         = default;
  FooImp(const FooImp&)            = delete;
  FooImp& operator=(const FooImp&) = delete;
  void Execute() const override {
    std::cout << "Foo::Execute()" << std::endl;
  }
};

std::shared_ptr<const Foo> MakeFoo() {
  return std::make_shared<const FooImp>();
}

关于class Foo中受保护的非虚拟析构函数,文档声明:

  

请注意上面示例中的受保护和非虚拟析构函数。该   客户端代码不能,也不需要删除指向X的指针;该   从shared_ptr<X>返回的createX实例将正确调用   ~X_impl

我相信我理解。

现在,在我看来,如果工厂函数返回std::unique_ptr<Foo>,这个好的习惯用法可以用来生成类似单身的实体;用户将被迫move指针,编译时保证不存在副本。

但是,唉,除非我将~Foo() = defaultprotected更改为public,否则我无法使代码生效,我不明白为什么。

换句话说,这不起作用:

std::unique_ptr<const Foo> MakeUniqueFoo() {
    return std::make_unique<const FooImp>();
}

我的问题:

  1. 你能解释一下我为什么需要public ~Foo() = default
  2. 仅删除protected
  3. 会不会有危险
  4. 单身人士的想法是否值得呢?

3 个答案:

答案 0 :(得分:6)

  1. 问题与删除器在智能指针中的工作方式有关。

    shared_ptr中,删除器是动态的。当您拥有std::make_shared<const FooImp>();时,该对象中的删除者将直接调用~FooImpl()

      

    使用delete-expression或在构造期间提供给shared_ptr 的自定义删除器来销毁对象。

    该删除器将在创建时复制到shared_ptr<const Foo>

    unique_ptr中,删除器是类型的一部分。它&#39; S:

    template<
        class T,
        class Deleter = std::default_delete<T>
    > class unique_ptr;
    

    所以当你有unique_ptr<const Foo>时,会直接拨打~Foo() - 这是不可能的,因为~Foo()protected。这就是为什么当你Foo()公开时,它有效。作品,如,编译。您也必须将其设为virtual - 否则您只会破坏Foo的{​​{1}}部分,从而导致行为未定义。

  2. 这并不危险。除非你忘记将析构函数设为虚拟,否则它会重复,会导致未定义的行为。

  3. 这并不像单身人士一样。至于它是否值得吗?主要是基于意见。

答案 1 :(得分:3)

每个shared_ptr存储4件事:指针,强引用计数,弱引用计数和删除器。

删除器获取构造shared_ptr的类型,并删除那个类型,而不是公开的类型。如果将其强制转换为基础shared_ptr,则仍会存储派生的删除器。

默认情况下,

unique_ptr不存储这样的有状态删除器。

这背后的设计原因是shared_ptr已经在管理额外的资源:假设您已经在管理引用计数,那么添加删除器很便宜。

对于unique_ptr,没有有状态删除器,它的开销基本上与原始指针相同。默认添加有状态删除器会使unique_ptr显着更加昂贵。

虽然它们都是智能指针,但unique_ptr真的很小,而shared_ptr则更复杂。

您可以通过向unique_ptr添加有状态删除来解决此问题。

struct stateful_delete {
  void const* ptr = nullptr;
  void(*f)(void const*) = nullptr;
  template<class T>
  stateful_delete(T const* t):
    ptr(t),
    f([](void const* ptr){
      delete static_cast<T const*>(ptr);
    })
  {}
  template<class T>
  void operator()(T*)const{
    if (f) f(ptr);
  }
};

template<class T>
using unique_ptr_2 = std::unique_ptr<T, stateful_delete>;

template<class T>
unique_ptr_2<T> unique_wrap_2(T* t) {
  return {t, t};
}
template<class T, class...Args>
unique_ptr_2<T> make_unique_2(Args&&...args) {
  return unique_wrap( new T(std::forward<Args>(args)...) );
}

这样的unique_ptr_2unique_ptr的3倍。他们不做额外的分配(与shared_ptr不同)。他们将与您的非虚拟保护~Foo一起使用公开~FooImpl

如果我们使用unique_ptr_2技术进行统一分配,您可以将make_shared的大小减少到2个指针,并存储等效的ptr和{{1}在堆上。我不确定这种复杂性是否值得节省。

答案 2 :(得分:0)

根据Barry的回答,将其公开的替代方法是定义您自己的删除器,该删除器可以访问您的班级“~Foo()方法。”

示例(尝试使用VS2013):

template <typename T>
class deleter
{
public:
    void operator()(T* a)
    {
        // Explicitly call the destructor on a.
        a->~A();
    }
};

class A {
    friend class deleter<A>; // Grant access to the deleter.
protected:
    ~A() {
        // Destructor.
    }
};

std::unique_ptr<A, deleter<A>> MakeA()
{
    return std::unique_ptr<A, deleter<A>>(new A());
}
相关问题