我的一个应用程序将极大地受益于std::unique_ptr<T>
的一个变体,它可以配置为不总是假定所指向的对象的所有权。
考虑以下类层次结构:
class AbstractFoo { ... };
template<typename T> Foo : public AbstractFoo
{
Foo( const AbstractFoo& absFoo ) { ... }
...
};
和一个API,它对每个接受AbstractFoo
的例程进行标准化,并在必要时转换为Foo<T>
的特定实例。在对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<T>
有一个类似于std::unique_ptr<T>
的构造函数,但有一个可选的布尔参数,指定指针是否应该由智能指针拥有。
对于这种情况是否有最佳实践解决方案?我宁愿避免返回原始指针,因为如果在手动删除对象之前抛出异常,可能会导致内存泄漏。
您可以将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
}
Update: programmerjake显然在我打字的时候在评论中写了同样的想法。如果你想把它作为答案写下来,我就把我的删掉。
我认为这个设计是破碎的脆弱的:
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建议)…也许Convert
可以被称为smart_foo_cast
或类似的东西,那么生命周期的事情在它的名字中会更好地体现出来。
就我个人而言,我也会让它采取AbstractFoo*
(这种变化不会影响外部API)。只要确保永远不要使用const AbstractFoo&
,因为您永远不知道const&
何时可能是隐式临时的。
你的问题就会消失,如果你可以(可选)通过使用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 );