8

假设我有一堂课。

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

然后我做:

BigDataPtr bigDataPtr(new BigData());

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

执行以下操作是否安全:

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

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

4

4 回答 4

6

有几种方法可以解决这个问题。您可以使用placement new,这可以保证是安全的,原因有两个:

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

  2. shared_ptr是非侵入性的;它的唯一职责是计算引用并在必要时调用删除器。

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

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

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

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

*bigDataPtr = BigData(42);

或将reset()成员函数添加到BigData

bigDataPtr->reset(42);

然后,您的真正意图就很明确了,您无需担心对象的生命周期。

于 2013-04-03T21:05:25.923 回答
2

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

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

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

IfbigDataPtr在线程之间共享,这也可能导致竞争条件,除非使用锁定规则。

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

如果您担心new (&*bigDataPtr) BigData;语句中的可疑取消引用,请改用普通指针:

BigData* p = bigDataPtr.get();
p->~BigData();
new (p) BigData;
于 2013-04-03T20:59:19.860 回答
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;

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

于 2013-04-03T20:59:31.160 回答
1

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

因此,您必须通过使用 nothrow 构造函数或捕获任何异常并防止智能指针删除对象来处理这个问题。由于shared_ptr没有release()功能,说起来容易做起来难。如果一切都失败了,你可以打电话terminate(),但这不会让你受到用户的欢迎。

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

如果在对象的生命周期结束之后,在对象占用的存储空间被重用或释放之前,在原始对象占用的存储位置创建一个新对象,指向原始对象的指针......可以用于操作新对象,如果......原始对象的类型不是 const 限定的,并且,如果是类类型,不包含任何类型为 const 限定或引用类型的非静态数据成员...

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

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

于 2013-04-03T22:23:08.763 回答