假设我有一堂课。
class BigData {...};
typedef boost::shared_ptr<BigData> BigDataPtr;
然后我这样做:
BigDataPtr bigDataPtr(new BigData());
稍后我完成了我的对象后,我确信该对象没有其他用户。
执行以下操作是否安全:
bigDataPtr->~BigDataPtr();
new (&*bigDataPtr) BigData;
这会让我在没有任何额外分配的情况下重置对象吗?
答案 0 :(得分:6)
有几种方法可以解决这个问题。您可以使用新的展示位置,这可以保证安全,原因有两个:
您已经为对象分配了内存,因此您知道它的大小和对齐方式正确。
shared_ptr
是非侵入性的;它的唯一责任是计算参考文献并在必要时致电删除者。
但是,请考虑如果对象的重建失败会发生什么 - 即抛出异常:
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()
。当然,这可能不会那么快。