【发布时间】:2018-08-24 04:05:40
【问题描述】:
我很好奇在当前 C++ 标准下如何解释以下情况,尤其是在生命周期等方面。它是未定义的行为吗?
首先,让我们从以下定义开始:可重定位对象是一个在其实际内存位置上不变的对象——也就是说,无论指针 this 的值如何,它的状态都保持不变。假设我们有一个可重定位类型 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++ 似乎在大多数情况下将对象标识和对象存储视为同一件事,因此禁止这样做可能是有原因的。在此先感谢所有有见地的 cmets。
编辑:有人建议Why would the behavior of std::memcpy be undefined for objects that are not TriviallyCopyable? 是这个问题的副本。我不确定是不是。首先,我正在存储存储,而不是对象实例。其次,对于所有实际相关的应用程序,std::is_trivially_copyable<Relocatable>::value 实际上评估为true。
附:我问这个问题实际上有一个很好的实际原因。有时,拥有只能存在于其容器中的对象很有用——它们不可复制且不可移动。例如,我目前正在设计一个具有这样一个属性的优化树数据结构——树节点只能存在于树存储中,它们不能被移出或复制——对它们的所有操作都是通过短期引用执行的。为了防止程序员错误(意外复制/移动),我将删除复制和移动构造函数。这具有相当不幸的后果,即节点不能存储在 std::vector 中。放置新的和明确管理的存储可以用来绕过这个限制——但我当然不想 做一些不符合标准的事情。
【问题讨论】:
-
C++ 中没有可重定位的东西(无论如何现在)。只有微不足道的可复制。析构函数也不是对象生命周期结束的必要条件
-
一个对象是一个存储区域。是的,该标准将对象地址视为其身份。该对象不会继续存在于新地址,这是不可能的,新地址的任何东西都是新对象。为什么这很有趣或很重要?如果其他节点中有指向它们的指针,则您的树节点不能存在于可调整大小的数组中,因为可调整大小的数组会移动其内容并且指针不会跟踪它们的指针。无论语言或您将移动和复制的概念赋予何种精细语义,这都是正确的,
-
@n.m:他们当然可以住在可调整大小的存储中。很明显我不会使用实际的指针——节点引用存储为压缩偏移量(也提高了约 10% 的性能)。
-
那么实际使用移动构造函数来移动它们有什么问题呢?没有对 reinterpet_cast 或 memcpy 的 hideous 调用?
标签: c++ language-lawyer move-semantics