我最近关注了Reddit的一次讨论,该讨论对编译器之间的std::visit
优化进行了很好的比较。我注意到以下内容:https://godbolt.org/z/D2Q5ED
GCC9和Clang9(我猜它们共享相同的stdlib)都不会生成代码,用于在所有类型都满足某些条件时检查和抛出无值异常。这导致了更好的代码生成,因此我提出了MSVC STL的问题,并给出了以下代码:
template <class T>
struct valueless_hack {
struct tag {};
operator T() const { throw tag{}; }
};
template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
try { v.emplace<0>(valueless_hack<First>()); }
catch(typename valueless_hack<First>::tag const&) {}
}
声明是,这使得任何变体都没有价值,阅读文档应该是:
首先,销毁当前包含的值(如果有)。然后direct初始化包含的值,就像构造的值一样键入带有参数
std::forward<Args>(args)....
的T_I
如果抛出异常时,*this
可能会变成valueless_by_exception。
我不明白的是:为什么它被称为"可能"?如果整个行动失败,留在旧状态合法吗?因为GCC就是这么做的:
// For suitably-small, trivially copyable types we can create temporaries
// on the stack and then memcpy them into place.
template<typename _Tp>
struct _Never_valueless_alt
: __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
{ };
后来它(有条件地)做了一些类似的事情:
T tmp = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);
因此,基本上它创建了一个临时的,如果成功,则将其复制/移动到实际位置。
国际海事组织这违反了文件规定的"首先,销毁目前包含的价值"。当我阅读标准时,在v.emplace(...)
之后,变量中的当前值总是被破坏,新类型要么是集合类型,要么是无值类型。
我确实得到条件is_trivially_copyable
排除了所有具有可观察析构函数的类型。因此,这也可以是:"就好像用旧值重新初始化了变量"左右。但变量的状态是一种可观察的影响。那么,标准确实允许emplace
不改变当前值吗?
根据标准报价进行编辑:
然后初始化包含的值,就像直接非列表初始化具有参数
std::forward<Args>(args)...
的TI类型的值一样。
T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
真的算是上述内容的有效实现吗?这就是"好像"的意思吗?
我认为标准的重要部分是:
发件人https://timsong-cpp.github.io/cppwp/n4659/variant.mod#12
23.7.3.4修改
(…)
模板variant_alternative_t>&模板(Args&&…Args);
(…)如果在初始化包含的值期间抛出异常,则变体可能不包含值
它说"可能"而不是"必须"。我希望这是有意的,以便允许类似gcc所使用的实现。
正如您自己所提到的,只有当所有备选方案的析构函数都是琐碎的,因此不可观测时,这才有可能,因为需要销毁以前的值。
后续问题:
Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std::forward<Args>(args)....
是否执行tmp{std::正向(args)…};this->value=std::move(tmp);真的算是上述内容的有效实现吗?这就是"好像"的意思吗?
是的,因为对于一般可复制的类型,没有办法检测差异,所以实现的行为就像值是按照描述初始化的一样。如果该类型不是微不足道的可复制类型,那么这将不起作用。
因此,标准确实允许
emplace
不更改当前值?
是。emplace
应提供不泄漏的基本保证(即在施工和破坏产生可观察到的副作用时尊重物体寿命),但在可能的情况下,允许提供强有力的保证(即操作失败时保持原始状态)。
CCD_ 12的行为必须类似于并集—备选方案被分配在适当分配的存储器的一个区域中。不允许分配动态内存。因此,改变类型的emplace
无法在不调用额外的移动构造函数的情况下保留原始对象—它必须摧毁它并构造新的对象来代替它。如果这种构造失败,那么变体必须进入异常的无值状态。这样可以防止诸如破坏不存在的对象之类的奇怪事情。
然而,对于小的、琐碎的可复制类型,可以在没有太多开销的情况下提供强大的保证(在这种情况下,甚至可以提高避免检查的性能)。因此,实施做到了。这是符合标准的:实施仍然提供了标准要求的基本保证,只是以一种更方便用户的方式。
根据标准报价进行编辑:
然后初始化包含的值,如同直接非列表初始化具有参数的TI类型的值
std::forward<Args>(args)...
。
T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
真的算是上述内容的有效实现吗?这就是"好像"的意思吗?
是的,如果移动分配没有产生可观察到的效果,那么对于普通的可复制类型就是这样。