C ++标准对移动实时对象存储有何看法?

时间:2018-03-15 11:07:25

标签: c++ language-lawyer move-semantics

我很好奇在当前的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中。放置新的和显式管理的存储可以用来绕过这个限制 - 但当然我不想这样做 根据标准做一些不合适的事情。

1 个答案:

答案 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的调用就像

    一样      
        
    1. 将源存储复制到临时区域

    2.   
    3. 隐式在目标存储中创建对象,然后

    4.   
    5. 将临时存储复制到目标存储。

    6.         

      这允许memmove保留可复制对象的类型,或者用于将一个对象的字节表示重新解释为另一个对象的字节表示。

        
    7. memcpy的调用与调用memmove的行为相同,只是它在源和目的地之间引入了重叠限制。

    8.   

这个提议(或类似的东西)将是您的代码格式良好的必要条件。