C++ 使用不可复制(但可移动)键移动地图分配时出错

C++ 使用不可复制(但可移动)键移动地图分配时出错,c++,c++14,move-semantics,stdmap,C++,C++14,Move Semantics,Stdmap,为什么这不起作用: #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(); retu

为什么这不起作用:

#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;
}
要查看的整个错误消息

在我看来,
std::pair
的默认移动构造函数试图使用
std::unique\u ptr
的副本构造函数。我假设map assignment操作符使用将新映射内容的分配移到旧映射内容上,而
std::swap
不能这样做,因为它需要保持旧内容不变,所以它只交换内部数据指针,从而避免了问题

移动分配的必要性(至少能够)可能来自于C++11中的
allocator\u traits::propagate\u on\u container\u move\u assignment
,但我的印象是,在C++14中,整个事情是固定的。我不知道为什么STL会选择移动赋值元素,而不是在move assignment操作符中在容器之间交换数据指针

所有这些都不能解释为什么moved map中包含的对的移动赋值失败——当然不应该

顺便说一句:
g++-v

gcc version 4.9.2 (Ubuntu 4.9.2-0ubuntu1~14.04) 
允许要求将移动分配到地图的
值\u类型

理由:

从§23.4.4.1开始

对于
map
而言,key类型为key,value类型为pair

§23.2.3

5对于set和multiset,值类型与键类型相同。对于map和multimap,它等于

7关联容器满足分配器感知容器(23.2.1)的所有要求,但 对于map和multimap,表95中对value_类型的要求适用于key_类型 和映射的U类型。[注:例如,在某些情况下,需要输入key_type和mapped_type 即使关联的值\u类型pair不是 可转让副本-尾注]

从表95中可以看出:

表达方式:

a=rv

返回类型:

X&

操作语义:

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

断言/注释前置/后置条件:

a应等于分配前rv的值

复杂性:

线性的

因此,您需要提供一个const-Key&&move分配,以使其可移植

像这样:

#include <memory>
#include <map>

struct key {

  key(key&&);
  key(const key&&);
  key& operator=(key&&);
  key& operator=(const key&&);
};
bool operator<(const key& l, const key& r);

struct value {

};

using map_type = std::map<key, value>;

map_type foo();
map_type foo2();

int main(){
  auto barmap=foo();
  barmap = foo2();
  return 0;
}
#包括
#包括
结构键{
键(键&&);
键(常数键和&);
键和运算符=(键和);
键和运算符=(常数键和);
};
bool操作符我认为这是libstdc++中的一个bug实现质量问题。如果我们查看容器要求表(现在),其中一个要求是:

a = rv
其中,
a
X
(容器类)类型的值,
rv
表示
X
类型的非常量值。操作语义描述为:

a
的所有现有元素要么被指定移动,要么被销毁

声明如下:

map
满足集装箱的所有要求


其中一个要求是移动分配。显然,libstdc++的方法是移动赋值元素,即使
是不可复制的(这将使
不可移动-注意这里只与
的不可复制性相关)。但并没有强制要求搬家,这只是一种选择。请注意,该代码可以用libc++很好地编译 如果您进一步阅读表分配器感知容器要求,同一行显示(对于
a=rv
):

需要:If
allocator\u

可以看出,绝对不需要对
键类型
值类型
提出任何要求

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

那有什么用呢?它会帮助还是伤害
std::map
的客户

我个人的观点是,对不需要的客户类型提出要求只会让客户感到沮丧

<>我也相信C++标准的当前规范是如此复杂,以至于专家们也不能就规范所说的达成一致。这并不是因为专家是白痴。这是因为制定一个正确、明确的规范(在这种规模上)确实是一个非常困难的问题

最后,我认为目的是(或应该是)当规范冲突发生时,支持分配器的容器需求取代容器需求

最后一个复杂问题:在C++11中:

allocator_traits<allocator<T>>::propagate_on_container_move_assignment{} is false_type

allocator\u traits做了这个改变。

嗯,我想你已经改变了。您说第二个代码块可以工作,但随后您继续显示它的编译器错误。在第一个代码块中也没有
tmp
。在第二个示例中也没有定义
MyPtr
。@NathanOliver对不起,我的错。这两个代码实际上都有错误-现已修复。ideone.com上的错误实际上与第一个块代码略有不同,但相当于第一个块代码。这两个代码显然都无法编译,因为没有定义foo,但如果我甚至尝试将其链接到这里,那么我的问题就已经解决了。我省略了
foo
definition,因为我不想给编译器太多优化的机会来解决这个问题。我通读了C++14中的23.2.1,在我看来第一个代码是否可以工作,但这是一个相当复杂的部分,所以可能我忽略了一些代码对映射有价值的引用,这份复印作业怎么样?从
foo
生成的值是右值,因此应该使用移动赋值。使用自动导致这是移动建设-我知道它的工作,但什么
#include <memory>
#include <map>

struct key {

  key(key&&);
  key(const key&&);
  key& operator=(key&&);
  key& operator=(const key&&);
};
bool operator<(const key& l, const key& r);

struct value {

};

using map_type = std::map<key, value>;

map_type foo();
map_type foo2();

int main(){
  auto barmap=foo();
  barmap = foo2();
  return 0;
}
a = rv
allocator_traits<allocator<T>>::propagate_on_container_move_assignment{} is false_type
allocator_traits<allocator<T>>::propagate_on_container_move_assignment{} is true_type