我们可以在C ++ 17中检测到“琐碎的可重定位性”吗?

时间:2019-12-20 09:34:56

标签: c++ object memory c++17 typetraits

在将来的C ++标准中,我们将具有“平凡的可重定位性”的概念,这意味着我们可以简单地将字节从一个对象复制到未初始化的内存块中,而只需忽略/清零原始对象的字节即可。 这样,我们模仿了C风格的复制/移动对象的方式。

在将来的标准中,我们可能会使用类似std::is_trivially_relocatable<type>的类型特征。当前,我们拥有的最接近的东西是std::is_pod<type>,它将在C ++ 20中弃用。

我的问题是,在当前标准(C ++ 17)中,我们是否有办法弄清楚对象是否可重定位? 例如,std::unique_ptr<type>可以通过将其字节复制到新的内存地址并清零原始字节来进行移动,但是std::is_pod_v<std::unique_ptr<int>>false

此外,当前的标准要求是,每个未初始化的内存块都必须通过构造函数,才能被视为有效的C ++对象。即使我们能够以某种方式找出对象是否可重定位,如果我们只是移动字节,根据标准它仍然是UB。 因此,另一个问题是-即使我们能够检测到微不足道的可重定位性,如何在不导致UB的情况下实现微不足道的重定位?只需调用memcpy + memset(src,0,...)并将内存地址转换为正确的类型就是UB。 `

谢谢!

1 个答案:

答案 0 :(得分:4)

琐碎可重定位性的全部要点似乎是使对象能够按字节移动,即使存在非琐碎的move构造函数或move赋值运算符也是如此。即使在当前的提案P1144R3中,这最终也要求用户手动标记可能的类型。对于一个编译器来说,弄清楚一个给定的类型通常是否是可重定位的,最有可能等同于解决暂停问题(它必须了解并推理一个任意的,可能由用户定义的move构造函数或move赋值运算符的作用) )…

当然,您可以定义自己的is_trivially_relocatable特征,该特征默认为std::is_trivially_copyable_v,并让用户专注于应被视为可轻定位的类型。但是,即使这样也是有问题的,因为将无法自动将此属性传播到由可重定位类型组成的类型...

即使对于平凡可复制的类型,您也不能仅将对象表示形式的字节复制到某个随机存储器位置,然后将地址转换为指向原始对象类型的指针。由于从未创建过对象,因此该指针将不会指向对象。尝试访问指针未指向的对象将导致未定义的行为。简单可复制性意味着您可以将对象表示形式的字节从一个现有对象复制到另一个现有对象,并依靠它使一个对象的值等于另一个[basic.types]/3的值。

要执行此操作以轻松地重定位某些对象,将意味着您必须首先在目标位置构造一个给定类型的对象,然后将原始对象的字节复制到该对象中,然后在等效于如果您从该对象移开了会发生的情况。从本质上讲,这是一种仅移动对象的复杂方法……

之所以存在提议,是要在语言中添加琐碎可重定位性的建议是有原因的:因为您目前无法从语言本身中做到这一点……

请注意,尽管有所有这些,仅仅是因为编译器前端无法避免生成构造函数调用并不意味着优化器无法消除不必要的加载和存储。让我们看一下编译器为移动std::vectorstd::unique_ptr的示例生成的代码:

auto test1(void* dest, std::vector<int>& src)
{
    return new (dest) std::vector<int>(std::move(src));
}

auto test2(void* dest, std::unique_ptr<int>& src)
{
    return new (dest) std::unique_ptr<int>(std::move(src));
}

As you can see,仅仅进行实际的移动通常就可以归结为只是复制和覆盖一些字节,即使对于非平凡的类型……