我是P1144的作者;不知何故,我现在才看到这个SO问题!
std::is_trivially_relocatable<T>
被提议用于一些未来版本的C++,但我不预测它会很快得到认可(肯定不会在C++23中,我打赌不会在C++26中,很可能永远不会)。 这篇论文(P1144R6,2022年6月)应该能回答你很多问题,特别是那些人们正确回答说如果你已经能够在现有的C++中实现这个功能,我们就不需要提议了的问题。 另请参见我的2019 C++Now演讲。
迈克尔·肯泽尔的回答表示P1144“最终要求用户
手动标记可以进行[平凡重定位]的类型”;我想指出这与本意相反。对于平凡可重定位性而言,现代技术是需要手动标记(“保证”)每一个此类类型;例如,在Folly中,您可以这样说。
struct Widget {
std::string s;
std::vector<int> v;
};
FOLLY_ASSUME_FBVECTOR_COMPATIBLE(Widget);
这是一个问题,因为普通的行业程序员不应该费心地尝试弄清楚他们选择的库中std::string
是否可以被轻松重定位。(1.5个最大的3家供应商上述注释是错误的!)即使是Folly自己的维护者也无法在100%的时间内正确处理这些手动注释。
因此,P1144的想法是编译器可以为您解决此问题。您的工作从危险的保证您不一定知道的事情转变为仅(可选地)验证您希望成真的事情,通过使用static_assert
(Godbolt):
struct Widget {
std::string s;
std::vector<int> v;
};
static_assert(std::is_trivially_relocatable_v<Widget>);
struct Gadget {
std::string s;
std::list<int> v;
};
static_assert(!std::is_trivially_relocatable_v<Gadget>);
在您(原帖作者)的具体用例中,似乎您需要找出给定的lambda类型是否是可以平凡重定位的(
Godbolt):
void f(std::list<int> v) {
auto widget = [&]() { return v; };
auto gadget = [=]() { return v; };
static_assert(std::is_trivially_relocatable_v<decltype(widget)>);
static_assert(!std::is_trivially_relocatable_v<decltype(gadget)>);
}
这是一些你无法使用Folly/BSL/EASTL完成的事情,因为它们的保证机制仅适用于全局作用域中的命名类型。你不能完全像这样使用FOLLY_ASSUME_FBVECTOR_COMPATIBLE(decltype(widget))
。
在类似于std::function
的类型内部,你确实需要知道捕获类型是否可以被轻松重定位。但由于你无法知道这一点,下一个最好的选择(也是你应该实践的)是检查std::is_trivially_copyable
。这是目前被认可的类型特征,它的字面意思是“此类型安全地进行memcpy
,可以跳过析构函数” - 基本上你将要使用它的所有东西。即使你知道该类型恰好是std::unique_ptr<int>
,或其他什么,在当前的C++标准中使用memcpy复制它仍然是未定义行为,因为当前标准规定你不允许memcpy非平凡可复制的类型。
顺便说一句,技术上来讲,P1144并没有改变这个事实。P1144仅表示允许实现省略重定位的效果,这是对实现者的巨大暗示,让他们只需使用memcpy即可。但即使是P1144R6也不允许普通的非实现程序员对非平凡可复制类型进行memcpy:它为某些编译器实现和某些库实现打开了一个门,可以使用__builtin_trivial_relocate函数,在某种神奇的意义上与普通的memcpy有所区别。
最后,你的最后一段提到了memcpy + memset(src,0,...)。那是错误的。平凡重定位就相当于只是memcpy。如果你关心源对象之后的状态——例如,如果你关心它是全零字节——那就意味着你将再次查看它,这意味着你并没有把它视为已销毁,这意味着你并没有真正执行重定位的语义。"复制并将源置空"更常见的是移动的语义。重定位的目的是避免额外的工作。
std::unique_ptr
的实现并理解它是否可平凡重定位。听起来这就是你所要求的。 - Indiana Kernick