【问题标题】:Smart pointers which ambiguously point to a heap or stack object不明确地指向堆或堆栈对象的智能指针
【发布时间】:2014-08-15 20:11:17
【问题描述】:

我的一个应用程序将极大地受益于std::unique_ptr<T> 的变体,可以将其配置为不总是假定所指向对象的所有权。

考虑以下类层次结构:

class AbstractFoo { ... };

template<typename T> Foo : public AbstractFoo 
{
    Foo( const AbstractFoo& absFoo ) { ... } 
    ... 
};

和一个 API,它标准化每个例程接受 AbstractFoo 并根据需要转换为 Foo&lt;T&gt; 的特定实例。如果对 AbstractFoo 的引用实际上已经是正确派生类型的实例,则只需要 dynamic_cast 并且不需要复制数据。但是,当抽象引用的类型不正确时,需要执行重要的工作来创建请求格式的副本。

我想要的界面如下所示:

template<typename T>
my_unique_ptr<Foo<T>> Convert( AbstractFoo& absFoo )
{
    if( Foo<T>* foo = dynamic_cast<Foo<T>*>(&absFoo) )
        return my_unique_ptr<Foo<T>>( foo, false );
    else
        return my_unique_ptr<Foo<T>>( new Foo<T>(absFoo) );
}

void Bar( AbstractFoo& absFoo )
{
    my_unique_ptr<Foo<T>> ptr = Convert<T>( absFoo );
    ...
}

其中make_unique_ptr&lt;T&gt; 类具有类似于std::unique_ptr&lt;T&gt; 的构造函数,但带有一个可选的布尔参数,用于指定指针是否应由智能指针拥有。

对于这种情况,是否有最佳实践解决方案?我宁愿避免返回原始指针,因为如果在手动删除对象之前引发异常,它可能会导致内存泄漏。

【问题讨论】:

  • shared_ptr 有用吗? (您要么需要AbstractFoo 来实现enable_shared_from_this,要么将shared_ptr 传递给Convert 而不是引用)。
  • 是的,shared_ptr 似乎就是这种情况,它完全按照您的说法行事:并不总是承担所有权,而是采取相应的行为。您的布尔标志在shared_ptr 的引用计数器中有一个隐式等效项。其他可能性是即使类型正确也总是克隆对象,但如果可能的话,也许提供一种廉价且快速的克隆方法。
  • @dlf 根据the documentation,您建议的两种方法都要求对象已经由shared_ptr 管理。不幸的是,这非常具有侵入性,我曾希望智能指针的使用仅限于我的库实现中。
  • @JackPoulson 是的,这是真的。如果这会破坏交易,您将需要其他东西(例如 BartoszKP 建议始终创建克隆,但要降低克隆成本)。
  • 如何使用带有空删除器的智能指针?

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


【解决方案1】:

您可以将shared_ptr 与自定义删除器结合使用:

template<typename T>
shared_ptr<Foo<T>> Convert( AbstractFoo& absFoo )
{
    if( Foo<T>* foo = dynamic_cast<Foo<T>*>(&absFoo) )
        return shared_ptr<Foo<T>>( foo, [](Foo<T>*){} ); // do-nothing deleter
    else
        return make_shared<Foo<T>>( absFoo ); // regular deleter
}

更新:programjake 显然在我输入此内容时在评论中写了相同的想法。如果你想写它作为答案,我会删除我的。

【讨论】:

  • 只有在代码未能删除传递给Convert的原始AbstractFoo&amp;时才会泄漏,以前也是如此。否则,引用计数会像往常一样上下升降,直到达到 0,此时……什么都不会发生。 :)
  • @BartoszKP 不,返回的对象将与参数具有相同的生命周期
【解决方案2】:

我认为这个设计是破碎脆弱的:

template<typename T>
my_unique_ptr<Foo<T>> Convert( AbstractFoo& absFoo )
{
    if( Foo<T>* foo = dynamic_cast<Foo<T>*>(absFoo) )
        return my_unique_ptr<Foo<T>>( foo, false );
    else
        return my_unique_ptr<Foo<T>>( new Foo<T>(absFoo) );
}

if 路径中,您创建一个absFoo 参数的生命周期相关的对象

else 路径中,您可以创建一个与任何其他对象的生命周期无关的对象。

调用者无法区分这两种情况 - 这似乎相当脆弱。


至于仍在使用它(shared_ptr 就像dlf suggests)... 也许Convert 可以称为smart_foo_cast 或类似的名称,那么一生的事情在它的名字中会更明显。

就我个人而言,我还需要一个AbstractFoo*(该更改不会影响外部 API)。只需确保从不使用const AbstractFoo&amp;,因为您永远不知道const&amp; 何时可能是隐含的临时变量。


如果您可以(可选地)通过使用 unique_ptr 作为参数来“接收”参数,或者“共享”参数,那么您的问题就会消失。

// Caller always yields ownership of absFoo:
template<typename T>
unique_ptr<Foo<T>> Convert( unique_ptr<AbstractFoo> absFoo );

// Caller may yield ownership of absFoo:
// (Caller needs to check whether absFoo was moved-from)
template<typename T>
unique_ptr<Foo<T>> Convert( unique_ptr<AbstractFoo>& absFoo );

// Caller may share ownership of absFoo with return value:
template<typename T>
shared_ptr<Foo<T>> Convert( const shared_ptr<AbstractFoo>& absFoo );

【讨论】:

  • Convert 函数只是一个用于库内部使用的实用函数。要求使用智能指针包装输入具有外部效果。
  • 另外,你提出了一个关于不接受 const 引用输入的好观点,所以我修改了 the interface in the actual project 以接受指针。
猜你喜欢
  • 2013-08-19
  • 1970-01-01
  • 2019-12-20
  • 2012-02-05
  • 1970-01-01
  • 2012-10-07
  • 1970-01-01
  • 1970-01-01
  • 2017-05-06
相关资源
最近更新 更多