我有一个包含某种类型的对象的结构
T
,我知道它有一个 noexcept 默认构造函数。 我想在我的结构上公开一个成员函数,用新对象替换旧对象。 但是,T
可能没有赋值运算符,因此我需要销毁旧的T
并在同一个变量中构造一个新的T
。
因此,我想编写这样的代码:
template <typename T>
class Container {
private:
T t{};
public:
template <typename... Args>
void replace(Args&&... args) {
// destroy the old T...
std::destroy_at(&t);
// ... and construct a new T in its place (or if it throws, we default-construct a T)
try {
std::construct_at(&t, std::forward<Args>(args)...);
} catch {
std::construct_at(&t);
}
}
// stuff that we might do on the object, both before and after the call to replace()
void do_stuff() { t.do_stuff(); }
};
上面的代码是否有效?是否可以在替换操作之前和之后对对象执行操作而不调用未定义的行为? (或者也许这是未定义的行为,可以通过一些
std::launder
来明确定义?)
我不认为这部分是回答问题所必需的,但在我试图解决的具体问题中,
T
具有类似引用的语义,因此赋值类似于重新绑定引用,T
可能不允许。
请注意,this是一个类似的问题,但它获得的答案仅说明以下一项或多项:
T
的构造函数抛出异常时,你最终会得到垃圾。然而,我主要感兴趣的是我的代码是否根据标准定义良好,并且那里的答案都没有充分解决这个问题。 此外,在可以在标准 C++ 中实现
std::vector
之前就提出了链接的问题。
一般来说,是的,这很好,并且会像人们天真地期望的那样工作,没有任何限制。
但是,这只是因为代码中的特定情况,特别是您知道
t
是 T
的成员子对象。如果它只是任何 T
物体,那么它可能会有所不同。
此外,还有一个例外:如果
Container
对象是具有自动、静态或线程存储持续时间的 const
完整对象的子对象,则尝试构造新对象将导致未定义的行为。但考虑到在这种情况下,人们必须放弃 const
才能调用 replace
,这可能不是一个问题。