为什么需要STD ::变体才能成为“移动分配”中的有价值_by_exception?
我已经在cppReference上看到了以下注释,有关proiteless_by_exception
方法:
在以下情况下,变体可能变得毫无价值:
- (保证)在移动分配期间包含值的移动初始化期间,抛出了一个例外
- ((可选))在复制分配期间包含值的副本初始化期间抛出了一个例外
, 将抛出一个异常这样的
std::variant<MyClass> var = {...};
var = myClassObj;
不需要这样做var.valueless_by_exception()
等于true(大概,大概会在其先前的状态下留下var
),但是
std::variant<MyClass> var = {...};
var = std::move(myClassObj);
保证此代码可以使var.valueless_by_exception()
如果发生异常,则等于true。
在副本和移动分配之间规格上存在这种差异的实际原因是什么?
I have seen the following notes on cppreference regarding to the valueless_by_exception
method:
A variant may become valueless in the following situations:
- (guaranteed) an exception is thrown during the move initialization of the contained value during move assignment
- (optionally) an exception is thrown during the copy initialization of the contained value during copy assignment
So, the code like this
std::variant<MyClass> var = {...};
var = myClassObj;
is not required to make var.valueless_by_exception()
equal to true (and, presumably, would leave var
in its previous state), but this code
std::variant<MyClass> var = {...};
var = std::move(myClassObj);
is guaranteed to make var.valueless_by_exception()
equal to true if an exception happens.
What is the actual reason for such a difference in specifications between copy and move assignment?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

请注意,cppreference正在谈论不同的情况。当它说复制/移动分配时,它是从变体中谈论复制/移动分配,而不是来自
t
。从t
中分配的分配是在下一个语句中处理的:原因是从变体中失败的移动分配将始终留下目标变体有价值的是没有其他选项。
这里的问题是特定于分配的问题。当您将一个
变体
分配给另一个时,有两种可能性。这两个变体保持相同的ti
类型,或者它们没有。如果他们共享相同的
ti
,则可以直接发生在存储类型之间的复制/移动分配。发生这种情况时,变体提供了使用ti
的分配操作的例外保证。这种变体本身绝不变得毫无价值。它始终拥有一个实时ti
,该状态在失败的复制/移动分配运算符中,将其留在其中。但是,如果两个变体在
> ti
上有所不同必须破坏其当前对象,并通过复制/移动 construction (不是分配)创建源类型ti
的新对象。如果变体提供了强大的例外保证,那意味着如果此副本/移动构造失败,那么该变体仍应保留在分配运算符之前的对象类型和值。但是您会注意到,在创建新对象之前,该对象被销毁了。这是因为
variant
存储其所有ts
的联合,因此它们都共享内存。您不能尝试在变体中创建ti
,而不会先破坏那里的任何东西。一旦被摧毁,它就消失了,无法恢复。
因此,现在我们有了一个不持有原始类型的变体,并且我们在其中未能 create a a
ti
。所以...它有什么?没有什么。
从副本中毫无价值的原因是可选的,这是因为,如果一种类型在复制构造上抛出而不是在移动构造上(如许多投掷拷贝类型提供),则可以使用两个对象解决方案实现复制操作。在破坏内部变体对象之前,您将副本定位化对堆栈临时。如果成功的话,您可以销毁内部对象并从堆栈对象中移动构造。
委员会不想要求它也不想禁止。
Note that Cppreference is talking about a different situation. When it says copy/move assignment, it's talking about copy/move assignment from a variant, not from a
T
. Assignment fromT
is dealt with in the next statement:The reason failed move assignment from a variant will always leave the target variant valueless is that there's no other option.
The issue at hand here is something specific to assignment. When you are assigning one
variant
to the other, there are two possibilities. Either the two variants hold the sameTi
type, or they don't.If they share the same
Ti
, then a copy/move assignment between their stored types can happen directly. When that happens, variant provides the exception guarantee of the assignment operation for theTi
being used. At no point does the variant itself become valueless; it always holds a liveTi
which is in whatever state the failed copy/move assignment operator left it in.However, if the two variants differ on
Ti
, then the destination variant must destroy its current object and create a new object of the source typeTi
via copy/move construction (not assignment).If variant were to provide a strong exception guarantee, that would mean that if this copy/move construction fails, then the variant should still hold the object type and value it had before the assignment operator. But you'll notice that this object was destroyed before the creation of the new object. This is because
variant
stores a union of all of itsTs
, and thus they all share memory. You can't attempt to create aTi
in the variant without first destroying whatever was there.And once its destroyed, it's gone and cannot be recovered.
So now we have a variant that doesn't hold the original type, and we failed to create a
Ti
within it. So... what does it hold?Nothing.
The reason why going valueless from a copy is optional is that, if a type throws on copy construction but not on move construction (as many throwing-copy types provide), it is possible to implement the copy operation with a two object solution. You do the copy-initialization to a stack temporary before destroying the internal variant object. If that succeeds, you can destroy the internal object and move-construct from your stack object.
The committee didn't want to require this implementation, but it didn't want to forbid it either.