C++ 零法则:多态性删除和unique_ptr行为

C++ Rule of Zero : polymorphic deletion and unique_ptr behavior

提问人:Arun 提问时间:4/8/2014 最后编辑:phsArun 更新时间:4/8/2014 访问量:3489

问:

在最近的重载日志中,主题为“强制执行零规则”,作者描述了我们如何避免编写五运算符规则,因为编写它们的原因如下:

  1. 资源管理
  2. 多态性缺失

这两者都可以通过使用智能指针来解决。

在这里,我对第二部分特别感兴趣。

请考虑以下代码片段:

class Base
{
public:
    virtual void Fun() = 0;
};


class Derived : public Base
{
public:

    ~Derived()
    {
        cout << "Derived::~Derived\n";
    }

    void Fun()
    {
        cout << "Derived::Fun\n";
    }
};


int main()
{
    shared_ptr<Base> pB = make_shared<Derived>();
    pB->Fun();
}

在这种情况下,正如本文的作者所解释的那样,我们通过使用共享指针来获得多态删除,这确实有效。

但是如果我用 替换 ,我就不能再观察到多态性删除了。shared_ptrunique_ptr

现在我的问题是,为什么这两种行为不同?为什么处理多态性缺失而不处理?shared_ptrunique_ptr

C++ 多态性 指针 法则

评论

0赞 AliciaBytes 4/8/2014
你是如何初始化的?unique_pointer
0赞 Arun 4/8/2014
这有关系吗?反正像这样: unique_ptr<Base> pB(new Derived())
7赞 Captain Obvlious 4/8/2014
因为随身携带指向 deleter 函数的指针。当您将一个成员分配给兼容的成员时,指针是复制或移动的成员之一。这种情况不会发生在没有虚拟析构函数的基类上,并且由于您的基类没有虚拟析构函数,因此您会陷入困境。std::shared_ptrstd::shared_ptrstd::unique_ptr

答:

1赞 Yakk - Adam Nevraumont 4/8/2014 #1
template<typename T>
using smart_unique_ptr=std::unique_ptr<T,void(*)(void*)>;

template<class T, class...Args> smart_unique_ptr<T> make_smart_unique(Args&&...args) {
  return {new T(std::forward<Args>(args)...), [](void*t){delete (T*)t;}};
}

问题在于对存储指针的调用的默认删除程序。上面存储了一个在构造时知道类型的删除器,因此当复制到基类时仍将作为子类删除。unique_ptrdeleteunique_ptr

这增加了适度的开销,因为我们必须取消引用指针。此外,它还使类型非规范化,因为默认构造的 s 现在是非法的。你可以通过一些额外的工作来解决这个问题(用一个至少不会崩溃的半智能函子替换一个原始函数指针:但是,如果调用删除器时 是非空的,则应断言函数指针存在)。smart_unique_ptrunique

评论

0赞 Arun 4/8/2014
我使用了这个答案 stackoverflow.com/a/13512344/602798 的实现,但仍然没有给我多态删除
0赞 AndersK 4/8/2014
@Arun 你的虚拟dtor在哪里?
0赞 ooga 4/8/2014
@Arun,为什么 Base 中没有虚拟 dtor?
0赞 Arun 4/8/2014
因为这就是重点。我知道我需要一个虚拟析构函数来获得多态破坏。但如果我有shared_ptr,我就能侥幸逃脱......那为什么不用unique_ptr......这是我的问题!
0赞 Yakk - Adam Nevraumont 4/8/2014
@arun我做了更多的事情,并弄清楚了哪里出了问题。代码是第一次通过,但想法应该有效......default_delete
3赞 AliciaBytes 4/8/2014 #2

如果您使用 C++14 或像 Yakk 的答案那样编写自己的 14,它会起作用。基本上,共享指针行为之间的区别在于,您可以获得:make_unique

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

正如你所看到的,删除器属于该类型。如果声明 a,它将始终用作默认值。但会注意为您的类使用正确的删除器。unique_pointerunique_pointer<Base>std::default_delete<Base>make_unique

使用时,您可以获得:shared_ptr

template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );

和其他重载作为构造函数。正如你所看到的,默认的删除器在声明类型时取决于模板参数(除非你使用 ),而对于删除器取决于传递给构造函数的类型。unique_ptrmake_uniqueshared_ptr


您可以在此处看到一个允许使用虚拟析构函数进行多态删除的版本(此版本也应该在 VS2012 中工作)。请注意,它被黑客入侵了,我目前不确定 和 在 C++14 中的行为会是什么样子,但我希望他们能使这更容易。也许我会查看C++14添加的论文,看看如果我以后有时间,是否会有什么变化。unique_ptrmake_shared

评论

0赞 Arun 4/8/2014
这是否意味着如果我创建这样的shared_ptr shared_ptr<Base> pB(new Derived());我不会得到多态删除?
0赞 Arun 4/8/2014
我尝试创建一个shared_ptr,如我之前的评论中所述。但我仍然得到多态删除(顺便说一句,我正在使用 VS2012,希望它不是一些 VS 特定的非标准行为)
4赞 Florian Richoux 4/8/2014 #3

答案在这里:https://stackoverflow.com/a/22861890/2007142

报价:

一旦最后一个引用超出范围或被重置,将被调用并释放内存。因此,您不需要进行虚拟化。 并且不要提供此功能,因为它们不提供与删除器相关的机制,因为唯一指针要简单得多,并且旨在降低开销,因此不存储删除器所需的额外函数指针。shared_ptr~Derived()~Base()unique_ptr<Base>make_unique<Derived>shared_ptr

评论

0赞 Yakk - Adam Nevraumont 4/8/2014
然而,您可以设置与默认类型不同的删除程序类型,它似乎读起来好像它会被复制......