我可以使用placement new来重置shared_ptr中的对象吗?

时间:2013-04-03 20:49:21

标签: c++ performance memory

假设我有一堂课。

class BigData {...};
typedef boost::shared_ptr<BigData> BigDataPtr; 

然后我这样做:

BigDataPtr bigDataPtr(new BigData());

稍后我完成了我的对象后,我确信该对象没有其他用户。

执行以下操作是否安全:

bigDataPtr->~BigDataPtr();
new (&*bigDataPtr) BigData;

这会让我在没有任何额外分配的情况下重置对象吗?

4 个答案:

答案 0 :(得分:6)

有几种方法可以解决这个问题。您可以使用新的展示位置,这可以保证安全,原因有两个:

  1. 您已经为对象分配了内存,因此您知道它的大小和对齐方式正确。

  2. shared_ptr是非侵入性的;它的唯一责任是计算参考文献并在必要时致电删除者。

  3. 但是,请考虑如果对象的重建失败会发生什么 - 即抛出异常:

    bigDataPtr->~BigDataPtr();
    new (bigDataPtr.get()) BigData;
    

    然后你有一个问题:可以在非构造对象上调用删除器,几乎肯定会导致未定义的行为。我说“差不多”,因为删除者可能是一个无操作者,在这种情况下一切都会很好。

    我认为更安全的是将新值移动到现有对象中:

    *bigDataPtr = BigData(42);
    

    或者向reset()添加BigData成员函数:

    bigDataPtr->reset(42);
    

    然后明确你的真实意图是什么,你不需要关心对象的生命周期。

答案 1 :(得分:2)

如果BigData构造函数和析构函数不抛出异常并且线程之间没有共享bigDataPtr并且BigData的动态分配成员(如果有)没有指针或引用,则是安全的

如果析构函数抛出一个异常,你可能最终得到一个部分被破坏的对象(通常不推荐抛出析构函数,标准容器要求元素的析构函数不抛出)。

如果构造函数抛出,则最终可能会破坏对象,但不会构造新对象。

如果线程之间共享bigDataPtr,除非使用锁定规则,否则可能会导致竞争条件。

如果其他地方的代码接受BigData动态分配成员的引用或指针,则当它创建新的BigData时,其动态分配的成员可能会在其他地址分配,因此现有指针和对成员的引用变得无效。

如果您关注new (&*bigDataPtr) BigData;语句中的可疑解除引用,请改用普通指针:

BigData* p = bigDataPtr.get();
p->~BigData();
new (p) BigData;

答案 2 :(得分:2)

是的,通常是安全的。 (对Maxim Yegorushkin关于投掷边缘案例的观察的点头)

请注意下面的拼写错误消息

Boost将取消引用和->运算符定义为

template<class T>
typename boost::detail::sp_dereference< T >::type boost::shared_ptr< T >::operator* () const;

template<class T>
typename boost::detail::sp_member_access< T >::type boost::shared_ptr< T >::operator-> () const;

当解析了那些detail位时,你就有了这个

template<class T>
T & boost::shared_ptr< T >::operator* () const

template<class T>
T * boost::shared_ptr< T >::operator-> () const 

所以你直接处理指向对象。没有代理或其他构造可能会干扰您正在尝试的内容。

就指向数据而言,您的代码是:

bigDataPtr->~BigDataPtr();
new (&*bigDataPtr) BigData;

可能有拼写错误。但如果你打算:

bigDataPtr->~BigData();
new (&*bigDataPtr) BigData;

它将解析为

(BigData pointer)->~BigData();
new (&(BigData reference)) BigData;

这是合法的,你是正确的,它可以避免通常在分配时产生的额外分配。

答案 3 :(得分:1)

首先,如果构造函数抛出并且类不是简单的可破坏的那么你就会遇到问题,因为shared_ptr“想要”删除它,这会引起UB。

所以你必须通过使用nothrow构造函数或捕获任何异常并阻止智能指针删除对象来处理它。由于shared_ptr没有release()函数,因此说起来容易做起来难。如果所有其他方法都失败了,您可以致电terminate(),但这不会让您受到用户欢迎。

如果没有对该对象的其他引用,那么只要该类没有const或引用非静态数据成员(包括成员成员),它就会起作用。原因是3.8 / 7:

  

如果在对象的生命周期结束之后和存储之前   对象占用的是重用或释放的,一个新的对象是   在原始对象占用的存储位置创建,a   指向原始对象的指针...可用于   如果...原始对象的类型是,则操纵新对象   不是const-quali fi ed,并且,如果类类型,不包含任何   类型为const-quali fi ed的非静态数据成员或引用   键入...

请注意,shared_ptr只包含这样一个指针,它将用于操作新对象,如果3.8 / 7中的任何条件被破坏,则为UB。唯一可能被打破的是这一个,你已经用你所说的关于代码的内容覆盖了其余部分。特别是,您需要将原始对象创建为BigData的实例,而不是派生自BigData的类,因为新对象需要具有相同的最大值 - 来自旧的类型。

通常有更强大的方法来重置对象。例如,实现operator=(复制或移动赋值运算符),然后编写*bigDataPtr = BigData()。当然,这可能不会那么快。