C++11 避免std::shuffle中的自赋值
在使用检查过的glibcxx实现时,我偶然发现了以下问题:C++11 避免std::shuffle中的自赋值,c++11,g++,C++11,G++,在使用检查过的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 &l
/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 = {{{1}}, {{1, 2}}};
std::shuffle(intVectors.begin(), intVectors.end(), std::mt19937());
}
由于类型
没有显式地给出运算符=(&&&&)
,因此默认情况下,它是通过“递归地”对其成员应用相同的操作来实现的
问题发生在交换代码的第2行,其中\uuuuu a
和\uuuu b
指向相同的对象,导致代码\uuuu a.operator=(std::move(\uu a))
生效,然后触发选中的向量::operator=(&&&&)
实现中的错误
我的问题是:这是谁的错
- 它是我的吗?因为我应该为
提供一个实现,使“自交换”成为swap
NOP
- 是不是因为它不应该尝试与自身交换一个元素而使用std::shuffle
- 是否是检查过的实现,因为自移动分配是完全正确的
- 一切都是正确的,检查过的实现只是帮了我一个忙,让我做这个额外的检查(但是如何关闭它)
#include <vector>
int main() {
std::vector<int> vectorOfInts;
vectorOfInts = std::move(vectorOfInts);
}
#包括
int main(){
std::向量向量;
vectorOfInts=std::move(vectorOfInts);
}
当然,这是很明显的(为什么要将向量移动到自身?)。
如果您直接在其中交换
std::vector
s,则不会发生错误,因为vector类有一个交换函数的自定义实现,该函数不使用运算符=(&&&)
我读了一些关于复制构造函数和移动赋值等内容的教程(例如)。他们都说对象必须检查自我分配,在这种情况下什么也不做。因此,我想说这是checked实现的错误,因为self-move-assignment非常好。这是GCC checked实现中的一个bug。根据,可交换的要求包括(重点):
17.6.3.2§4当且仅当t
分别可与t
类型的任何右值或左值交换时,右值或左值t
才可交换
根据定义,任何右值或左值都包括t
本身,因此要可交换swap(t,t)
必须是合法的。同时,默认的swap
实现需要以下内容
20.2.2§2要求:类型T
应可移动(表20)和可移动(表22)
因此,要在默认交换运算符的定义下可交换,自移动分配必须有效,并且具有后条件,即自分配后的t
等于其旧值(但不一定是无操作!),如表22所示
虽然您要交换的对象不是标准类型,但MoveAssignable没有先决条件,即
rv
和t
引用不同的对象,并且只要所有成员都是可移动分配的(如std::vector
应该是),则生成移动分配运算符必须正确(根据12.8§29执行会员移动分配时)。此外,尽管注释中指出,rv
具有有效但未指定的状态,但任何状态(除了与其原始值相等)对于自赋值都是不正确的,否则将违反后条件。libstdc++调试模式断言基于标准中的此规则,来自[res.on.arguments]
如果函数参数绑定到右值引用参数,则实现可能会假定此参数是此参数的唯一引用
i、 e.实现可以假设对象绑定到T::operator=(T&&)的参数
不别名*此
,如果程序违反该假设,则行为未定义。因此,如果调试模式检测到右值引用实际上绑定到*此
,则它已检测到未定义的行为,因此可以中止
该段还包含本说明(重点):
[注意:如果程序将左值强制转换为xvalue,同时将该左值传递给库函数(例如,通过使用参数调用函数
std::move(x)
),程序有效地要求该函数将该左值视为临时对象。该实现可自由优化消除别名检查,如果
参数是左值。-结束注释]
i、 e.如果您说x=std::move(x)
,那么实现可以优化任何别名检查,例如:
X::operator=(X&& rval) { if (&rval != this) ...
由于实现可以优化这种检查,标准库类型一开始甚至不必费心做这样的检查,它们只是假设未定义自移动分配
但是,由于自移动分配可能出现在非常无辜的代码中(甚至可能超出用户的控制范围,因为std::lib执行自交换),因此标准被更改。不过,我认为DR的解决方案实际上没有帮助。它不会改变[res.on.arguments]中的任何内容这意味着执行自我移动任务仍然是未定义的行为,至少在解决之前。显然,C++标准委员会认为自移分配不应该导致未定义的行为(即使他们迄今为止还没有在标准中这样说)。因此,我们的调试模式仍然包含防止自移动分配的断言
在我们从libstdc++中删除Overager检查之前,您可以通过在包含任何其他头之前执行此操作来禁用单个断言(但仍保留所有其他调试模式检查):
<
X::operator=(X&& rval) { if (&rval != this) ...
#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)='