我很好奇在当前的C ++标准下如何解释以下情况,特别是在生命周期等方面。是不确定的行为?
首先,让我们从以下定义开始:可重定位目标文件是一个在其实际内存位置不变的对象 - 也就是说,无论指针的值如何,它的状态都保持不变。假设我们有一个可重定位类型Relocatable(它的定义与示例无关)。
然后我们有以下代码(C ++ 17):
typedef std::aligned_storage_t<sizeof(Relocatable)> Storage;
// construct an instance of a relocatable within a storage
auto storage0 = new Storage();
new(storage0) Relocatable(...);
{
// obj is a valid reference
// should use std::launder() here, but clang doesn't have it yet
Relocatable& obj = *reinterpret_cast<Relocatable*>(storage0);
}
// move the storage
auto storage1 = new Storage();
memcpy(storage1, storage0, sizeof(Storage));
delete storage0;
{
// ?????? what does the standard say about this?
Relocatable& obj = *reinterpret_cast<Relocatable*>(storage1);
}
这符合预期的GCC和Clang(对象只是继续存在于新存储中)。但是,我不完全确定标准是否可以。从技术上讲,对象的生命周期尚未结束(析构函数未被调用),并且在memcpy()调用之后,旧位置中没有对该对象的任何访问权限。此外,不存在旧位置的引用/指针。尽管如此,考虑到C ++似乎大多数时候都将对象标识和对象存储视为同一个东西,但可能有一个原因可以解释为什么这样做。提前感谢所有有见地的评论。
编辑:有人建议Why would the behavior of std::memcpy be undefined for objects that are not TriviallyCopyable?与此问题重复。我不确定是不是。首先,我是memcpying存储,而不是对象实例。其次,std::is_trivially_copyable<Relocatable>::value
实际上对所有实际相关的应用程序评估为true
。
P.S。我问这个问题实际上有一个很好的实际原因。有时,拥有只能存在于其容器中的对象是有用的 - 它们不可复制且不可移动。例如,我目前正在设计具有这种属性的优化树数据结构 - 树节点只能存在于树存储中,它们不能被移出或复制 - 它们上的所有操作都是通过短期引用来执行的。为了防止程序员错误(意外复制/移动),我删除了复制和移动构造函数。这具有相当不幸的结果,即节点不能存储在std :: vector中。放置新的和显式管理的存储可以用来绕过这个限制 - 但当然我不想这样做 根据标准做一些不合适的事情。
答案 0 :(得分:4)
因此,与所有这些问题一样,对象仅在four situations中创建:
对象是由定义([basic.def]), new-expression 创建的,当隐式更改union的活动成员([class.union])时,或者创建一个临时对象([conv.rval],[class.temporary])。
此代码:
auto storage1 = new Storage();
memcpy(storage1, storage0, sizeof(Storage));
在Storage
为您提供storage1
类型的对象,但此时没有创建Relocatable
类型的对象。因此,这个:
Relocatable& obj = *reinterpret_cast<Relocatable*>(storage1);
是未定义的行为。周期。
为了定义行为,我们需要第五种机制来创建一个对象,例如P0593中提出的内容:
我们建议至少将以下操作指定为隐式创建对象:[...]
对
一样memmove
的调用就像
将源存储复制到临时区域
隐式在目标存储中创建对象,然后
- 醇>
将临时存储复制到目标存储。
这允许
memmove
保留可复制对象的类型,或者用于将一个对象的字节表示重新解释为另一个对象的字节表示。对
memcpy
的调用与调用memmove
的行为相同,只是它在源和目的地之间引入了重叠限制。
这个提议(或类似的东西)将是您的代码格式良好的必要条件。