通过移动分配具有不可复制(但可移动)键的映射的错误

j_k*_*bik 8 c++ stdmap move-semantics c++14

为什么这不起作用:

#include <memory>
#include <map>

std::map<std::unique_ptr<char>, std::unique_ptr<int>> foo();
std::map<std::unique_ptr<char>, std::unique_ptr<int>> barmap;

int main(){
  barmap=foo();
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

这样做:

#include <memory>
#include <map>

std::map<std::unique_ptr<char>, std::unique_ptr<int>> foo();
std::map<std::unique_ptr<char>, std::unique_ptr<int>> barmap;

int main(){

  std::map<std::unique_ptr<char>, std::unique_ptr<int>> tmp(foo());
  using std::swap;
  swap(barmap, tmp);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

这与地图中的键类型不可复制这一事实有关(std :: map是否需要?).编译时的相关错误行g++ -std=c++14:

/usr/include/c++/4.9/ext/new_allocator.h:120:4: error: use of deleted function ‘constexpr std::pair<_T1, _T2>::pair(std::pair<_T1, _T2>&&) [with _T1 = const std::unique_ptr<char>; _T2 = std::unique_ptr<int>]’
  { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
    ^
In file included from /usr/include/c++/4.9/bits/stl_algobase.h:64:0,
                 from /usr/include/c++/4.9/memory:62,
                 from pairMove.cpp:1:
/usr/include/c++/4.9/bits/stl_pair.h:128:17: note: ‘constexpr std::pair<_T1, _T2>::pair(std::pair<_T1, _T2>&&) [with _T1 = const std::unique_ptr<char>; _T2 = std::unique_ptr<int>]’ is implicitly deleted because the default definition would be ill-formed:
       constexpr pair(pair&&) = default;
                 ^
/usr/include/c++/4.9/bits/stl_pair.h:128:17: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = char; _Dp = std::default_delete<char>]’
In file included from /usr/include/c++/4.9/memory:81:0,
                 from pairMove.cpp:1:
/usr/include/c++/4.9/bits/unique_ptr.h:356:7: note: declared here
       unique_ptr(const unique_ptr&) = delete;
Run Code Online (Sandbox Code Playgroud)

在ideone上可以看到整个错误消息.

在我看来,默认的移动构造函数std::pair尝试使用的复制构造函数std::unique_ptr.我认为地图赋值运算符使用新映射内容的移动赋值而std::swap不是旧映射内容,因为它需要保持旧内容不变,所以它只是交换内部数据指针,因此避免了问题.

有必要(至少能)移动分配可能来自问题allocator_traits<M::allocator_type>::propagate_on_container_move_assignment在C++ 11,但我下的印象是,在C++ 14整件事是固定的.我不确定为什么STL会选择移动分配元素而不是仅仅在移动赋值运算符中交换容器之间的数据指针.

并且以上所有都没有解释为什么移动地图中包含的对的移动分配失败 - 恕我直言,它不应该.

顺便说一下g++ -v:

gcc version 4.9.2 (Ubuntu 4.9.2-0ubuntu1~14.04) 
Run Code Online (Sandbox Code Playgroud)

How*_*ant 5

对我来说,这看起来像是C++标准中规范的根本失败.规范在"不要重复自己"中走得太远,以至于变得难以理解和模棱两可(imho).

如果您进一步阅读表Allocator感知容器要求,则同一行说(for a = rv):

要求:如果allocator_traits<allocator_type>::propagate_on_container_move_assignment::valuefalse,TMoveInsertableXMoveAssignable.所有现有元素a都是移动分配或销毁.post:a应等rv于此赋值之前的值.

我想每个人都同意这std::map<std::unique_ptr<char>, std::unique_ptr<int>>是一个支持分配器的容器.那么问题就变成了:它的移动赋值算子有什么要求?

如果我们只是在看分配器感知容器的需求,然后MoveInsertableMoveAssignable只需要,如果allocator_traits<allocator_type>::propagate_on_container_move_assignment::valuefalse.这比容器需求表所述的要求更弱,该表规定所有元素必须MoveAssignable与分配器的属性无关.那么分配器感知容器是否也必须满足容器的更严格要求?

让我们放松一下这个标准应该说的话,如果不是那么努力不重复自己.

实施需要什么?

如果allocator_traits<allocator_type>::propagate_on_container_move_assignment::value是,true那么在移动分配期间,所有内存资源的所有权都可以从rhs转移到lhs.这意味着map移动赋值只能执行O(1)指针以完成移动分配(当可以转移内存所有权时).指针twiddling不需要指针指向的对象上的任何操作.

下面是的libc ++实现的map任务时allocator_traits<allocator_type>::propagate_on_container_move_assignment::valuetrue:

https://github.com/llvm-mirror/libcxx/blob/master/include/__tree#L1531-L1551

人们可以看到,绝对没有任何要求需要放在key_typevalue_type.

我们应该人为地对这些类型提出要求吗?

这有什么用途?它会帮助或伤害客户std::map吗?

我个人认为,对不需要的客户类型提出要求只会让客户感到沮丧.

我也相信C++标准的当前规范风格是如此令人费解,甚至专家也无法就规范所说的内容达成一致.这不是因为专家是白痴.这是因为制定正确,明确的规范(按此规模)确实是一个非常困难的问题.

最后,我认为,当出现规范冲突时,意图是(或应该是)Allocator感知容器要求取代Container要求.

最后一个复杂因素:在C++ 11中:

allocator_traits<allocator<T>>::propagate_on_container_move_assignment{} is false_type
Run Code Online (Sandbox Code Playgroud)

在C++ 14中的位置:

allocator_traits<allocator<T>>::propagate_on_container_move_assignment{} is true_type
Run Code Online (Sandbox Code Playgroud)

因此libstdc ++行为符合C++ 11,并且libc ++行为符合C++ 14. LWG问题2103做出了这一改变.


Bar*_*rry 1

我相信这是libstdc++ 中实现问题的错误质量。如果我们查看容器要求表(现在为表 100),要求之一是:

a = rv
Run Code Online (Sandbox Code Playgroud)

其中是 type (容器类)a的值,表示 type 的非常量右值。操作语义描述为:XrvX

的所有现有元素a要么被移动分配要么被销毁

[map.overview]中指出:

Amap满足容器的所有要求

这些要求之一是移动分配。现在显然 libstdc++ 的方法是移动分配元素,即使在Key不可复制的情况下也是如此(这将导致pair<const Key, T>不可移动 - 请注意,Key这里仅与不可复制性相关)。但没有强制要求进行移动分配,这只是一种选择。请注意,该代码使用 libc++ 可以正常编译。