在使用glibcxx的检查实现时,我偶然发现了以下问题:
/usr/include/c++/4.8.2/debug/vector:159:error: attempt to self move assign.
Objects involved in the operation:
sequence "this" @ 0x0x1b3f088 {
type = NSt7__debug6vectorIiSaIiEEE;
}
我已经简化为这个最小的例子:
#include <vector>
#include <random>
#include <algorithm>
struct Type {
std::vector<int> ints;
};
int main() {
std::vector<Type> intVectors = {shuffle
, {{1, 2}}};
std::shuffle(intVectors.begin(), intVectors.end(), std::mt19937());
}
追踪问题我发现std::swap
想要Type
一个元素。由于std::swap
是用户定义的,并且没有为operator=(&&)
提供专门化,因此使用默认值创建临时值并使用 _Tp __tmp = _GLIBCXX_MOVE(__a);
__a = _GLIBCXX_MOVE(__b);
__b = _GLIBCXX_MOVE(__tmp);
来传输值:
Type
由于operator=(&&)
未明确提供__a
,因此默认情况下由&#34;递归地执行&#34;对其成员应用相同的操作。
问题出现在交换代码的第2行,其中__b
和__a.operator=(std::move(__a))
指向同一个对象,该对象在代码vector::operator=(&&)
中生效,然后在检查中触发错误实施swap
。
我的问题是:这是谁的错?
NOP
提供一个实现自我交换&#34;一个std::shuffle
?#include <vector>
int main() {
std::vector<int> vectorOfInts;
vectorOfInts = std::move(vectorOfInts);
}
&#39; s,因为它不应该尝试与自己交换元素?我读过有关要求迭代器为ValueSwappable的shuffle。这是否扩展到自交换(这只是运行时问题,无法通过编译时概念检查强制执行)?
要更直接地触发错误,可以使用:
std::vector
当然这很明显(你为什么要把一个矢量移到自己身上?)。
如果您直接交换operator=(&&)
,则不会发生错误,因为矢量类具有不使用{{1}}的交换函数的自定义实现。
答案 0 :(得分:5)
这是GCC检查实施中的一个错误。根据{{3}},可交换的要求包括(强调我的):
17.6.3.2§4当且仅当
t
可以分别与任何 rvalue或lvalue类型{{1}进行交换时,rvalue或左值t
是可交换的}}
根据定义,任何 rvalue或左值都包含T
本身,因此可以交换t
必须合法。同时,默认swap(t,t)
实现需要以下
20.2.2§2要求:类型
swap
应为MoveConstructible(表20)和MoveAssignable(表22)。
因此,要在默认交换运算符的定义下可交换,自动赋值必须是有效的并且具有自我赋值T
之后的后置条件等同于它的旧值(不一定是无操作值) !)如表22所示。
虽然您交换的对象不是标准类型,但MoveAssignable没有前提条件t
和rv
引用不同的对象,并且只要所有成员都是MoveAssignable({{1}应该是)生成移动赋值运算符必须是正确的(因为它按照12.8§29执行成员移动赋值)。此外,尽管该注释指出t
具有有效但未指定的状态,但除了等同于其原始值之外的任何状态对于自我赋值都是不正确的,否则后置条件将被违反。
答案 1 :(得分:5)
libstdc ++ Debug Mode断言基于此标准,来自[res.on.arguments]
如果函数参数绑定到右值引用参数,则实现可能会假定此参数是对此参数的唯一引用。
即。实现可以假设绑定到T::operator=(T&&)
参数的对象不是别名*this
,如果程序违反了该假设,则行为未定义。因此,如果调试模式检测到实际上右值引用 绑定到*this
,则它已检测到未定义的行为,因此可以中止。
该段落也包含这一说明(强调我的):
[注意:如果程序在将左值传递给库函数时将左值转换为x值(例如,通过使用参数调用函数)
std::move(x)
),该程序有效地要求该函数将该左值作为临时对象处理。 实现可以自由地优化别名检查,如果需要的话可能需要 参数是一个左值。 -end note]
即。如果你说x = std::move(x)
那么实现可以优化任何别名检查,例如:
X::operator=(X&& rval) { if (&rval != this) ...
由于实现可以优化该检查,标准库类型甚至不需要首先进行这样的检查。他们只是假设自动分配未定义。
但是,因为自动移动分配可能出现在相当无辜的代码中(可能甚至在用户控制之外,因为std :: lib执行自交换),标准由Defect Report 2468更改。我不认为该DR的解决方案确实有帮助。它不会改变[res.on.arguments]中的任何内容,这意味着执行自动移动分配仍然是未定义的行为,至少在issue 2839得到解决之前。很明显,C ++标准委员会认为自动移动分配不应该导致未定义的行为(即使它们到目前为止在标准中没有实际说明),所以我们的调试模式仍然是a libstdc++ bug包含阻止自动分配的断言。
在我们从libstdc ++中删除过时的检查之前,您可以通过执行此来禁用该单个断言(但仍保留所有其他调试模式检查),然后再包含任何其他标头:
#include <debug/macros.h>
#undef __glibcxx_check_self_move_assign
#define __glibcxx_check_self_move_assign(x)
或者等效地,仅使用命令行标志(因此无需更改源代码):
-D_GLIBCXX_DEBUG -include debug/macros.h -U__glibcxx_check_self_move_assign '-D__glibcxx_check_self_move_assign(x)='
这告诉编译器在文件的开头包含<debug/macros.h>
,然后取消定义执行self-move-assign断言的宏,然后将其重新定义为空。
(一般来说,定义,取消定义或重新定义libstdc ++的内部宏是未定义和不受支持的,但这样可行,并且有我的祝福。)
答案 2 :(得分:0)
我阅读了一些关于复制构造函数和移动赋值和东西的教程(例如this)。他们都说对象必须检查自我分配,在这种情况下什么都不做。所以我会说这是检查实现的错误,因为自动移动分配非常好。