当使用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;
}
Run Code Online (Sandbox Code Playgroud)
我已经减少到这个最小的例子:
#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());
}
Run Code Online (Sandbox Code Playgroud)
追踪问题我发现shuffle想要std::swap一个自己的元素.由于Type是用户定义的并且没有为其std::swap指定专门化,因此使用默认值创建临时operator=(&&)值并使用它来传输值:
_Tp __tmp = _GLIBCXX_MOVE(__a);
__a = _GLIBCXX_MOVE(__b);
__b = _GLIBCXX_MOVE(__tmp);
Run Code Online (Sandbox Code Playgroud)
由于Type没有明确给出operator=(&&)它默认是通过"递归"在其成员上应用相同的操作来实现的.
问题出现在交换代码的第2行,__a并__b指向同一个对象,该对象在代码中生效__a.operator=(std::move(__a)),然后在检查的实现中触发错误vector::operator=(&&).
我的问题是:这是谁的错?
swap,使"自我交换" NOP?std::shuffle,因为它不应该尝试与自己交换元素?我读过有关要求迭代器为ValueSwappable的shuffle.这是否扩展到自交换(这仅仅是运行时问题,并且不能通过编译时概念检查来强制执行)?
要更直接地触发错误,可以使用:
#include <vector>
int main() {
std::vector<int> vectorOfInts;
vectorOfInts = std::move(vectorOfInts);
}
Run Code Online (Sandbox Code Playgroud)
当然这很明显(你为什么要把一个矢量移到自己身上?).如果您std::vector直接交换s,则不会发生错误,因为vector类具有不使用的交换函数的自定义实现operator=(&&).
libstdc ++调试模式断言基于标准中的此规则,来自[res.on.arguments]
如果函数参数绑定到右值引用参数,则实现可以假定此参数是对此参数的唯一引用。
也就是说,实现可以假定绑定到参数的对象T::operator=(T&&)没有别名*this,并且如果程序违反了该假设,则行为是不确定的。因此,如果“调试模式”检测到实际上右值引用已绑定到*this它,则它检测到未定义的行为,因此可以中止。
本段也包含此注释(重点为我):
[注意:如果程序在将左值传递给库函数的同时将左值转换为x值(例如,通过使用参数调用该函数
std::move(x)),则该程序实际上是在要求该函数将该左值视为临时对象。该实现可以自由地优化消除混叠检查的功能,如果参数是左值,则可能需要进行混叠检查。 —尾注]
即,如果您说的x = std::move(x)话,该实现可以优化消除对别名的任何检查,例如:
X::operator=(X&& rval) { if (&rval != this) ...
Run Code Online (Sandbox Code Playgroud)
由于该实现可以优化检查,因此标准库类型甚至不必费心进行此类检查。他们只是假设自我移动分配未定义。
但是,由于自我移动分配可以在非常纯真的代码中出现(可能甚至在用户的控制之外,因为std :: lib执行自我交换),因此标准由缺陷报告2468进行了更改。我认为该DR的分辨率实际上并没有帮助。它不会更改[res.on.arguments]中的任何内容,这意味着执行自移动分配仍然是未定义的行为,至少直到问题2839得到解决为止。显然,C ++标准委员会认为自移动分配不应导致未定义的行为(即使到目前为止,他们实际上仍未真正说出这种行为),因此这是一个libstdc ++错误,我们的调试模式仍包含断言防止自我分配。
直到我们从libstdc ++中删除了过多的检查,您可以通过在包含任何其他标头之前执行以下操作来禁用单个断言(但仍保留所有其他调试模式检查):
#include <debug/macros.h>
#undef __glibcxx_check_self_move_assign
#define __glibcxx_check_self_move_assign(x)
Run Code Online (Sandbox Code Playgroud)
或等效地,仅使用命令行标志(因此无需更改源代码):
-D_GLIBCXX_DEBUG -include debug/macros.h -U__glibcxx_check_self_move_assign '-D__glibcxx_check_self_move_assign(x)='
Run Code Online (Sandbox Code Playgroud)
这告诉编译器<debug/macros.h>在文件的开始处包含它,然后取消定义执行自移动分配声明的宏,然后将其重新定义为空。
(通常,未定义和不支持定义,取消定义或重新定义libstdc ++的内部宏,但是这可以工作,并且我很高兴)。
这是GCC已检查的实现中的错误。根据C ++ 11标准,可交换要求包括(强调我的):
17.6.3.2§4
t当且仅当t可分别与任何类型的rvalue或lvalue互换的rvalue或lvalue才可互换T
根据定义,任何右值或左值t本身都包含在内,因此可交换swap(t,t)必须合法。同时,默认swap实现需要以下内容
T20.2.2§2 要求:类型应为MoveConstructible(表20)和MoveAssignable(表22)。
因此,要在默认交换运算符的定义下可交换,自我移动分配必须有效,并且具有以下条件:自我分配之后t等于其旧值(但不一定是无操作!),如表22所示。
尽管您要交换的对象不是标准类型,但是MoveAssignable没有前提条件rv并t可以引用不同的对象,并且只要所有成员都是MoveAssignable(std::vector应该如此),生成移动分配操作符就必须正确(因为它执行成员移动)根据12.8§29进行分配)。此外,尽管注释指出了rv有效但未指定的状态,但是任何与它的原始值等效的状态都将无法正确分配自定义值,否则将违反后置条件。
| 归档时间: |
|
| 查看次数: |
676 次 |
| 最近记录: |