为什么libc++的map实现使用这种联合?
Why does libc++'s implementation of map use this union?
#if __cplusplus >= 201103L
template <class _Key, class _Tp>
union __value_type
{
typedef _Key key_type;
typedef _Tp mapped_type;
typedef pair<const key_type, mapped_type> value_type;
typedef pair<key_type, mapped_type> __nc_value_type;
value_type __cc;
__nc_value_type __nc;
template <class ..._Args>
_LIBCPP_INLINE_VISIBILITY
__value_type(_Args&& ...__args)
: __cc(std::forward<_Args>(__args)...) {}
_LIBCPP_INLINE_VISIBILITY
__value_type(const __value_type& __v)
: __cc(__v.__cc) {}
_LIBCPP_INLINE_VISIBILITY
__value_type(__value_type& __v)
: __cc(__v.__cc) {}
_LIBCPP_INLINE_VISIBILITY
__value_type(__value_type&& __v)
: __nc(std::move(__v.__nc)) {}
_LIBCPP_INLINE_VISIBILITY
__value_type& operator=(const __value_type& __v)
{__nc = __v.__cc; return *this;}
_LIBCPP_INLINE_VISIBILITY
__value_type& operator=(__value_type&& __v)
{__nc = std::move(__v.__nc); return *this;}
_LIBCPP_INLINE_VISIBILITY
~__value_type() {__cc.~value_type();}
};
#else
// definition for C++03...
看起来目的是使__value_type
可分配和可移动,同时也能够将内容公开为pair<const key_type, mapped_type>
(这是迭代器的值类型等)。但我不明白为什么它需要可分配或可移动,因为我看不出有任何理由为什么实现需要在映射中复制或移动节点,或者确实做任何事情,而不是就地构造和销毁它们,并重新配置指针。
这是在支持Potatoswatter的答案。我作为这段libc++代码的作者回答。
考虑:
int
main()
{
std::map<A, int> m1;
m1[A{1}] = 1;
m1[A{2}] = 2;
m1[A{3}] = 3;
std::map<A, int> m2;
m2[A{4}] = 4;
m2[A{5}] = 5;
m2[A{6}] = 6;
std::cout << "start copy assignmentn";
m2 = m1;
std::cout << "end copy assignmentn";
}
在这种特殊情况下,我预见到需要回收映射的节点,并重新分配"const"键以使节点的回收有效。因此
http://cplusplus.github.io/LWG/lwg-defects.html # 704
插入以下措辞以允许map
节点的回收:
关联容器满足的所有要求分配器感知的容器(23.2.1 [container.requirements.general]),除了容器映射和multimap之外,其他要求都放在上面表93中的value_type直接应用于key_type和mapped_type。[注:例如key_type和mapped_type有时。即使value_type (pair)不是CopyAssignable,也需要是CopyAssignable。
从而允许容器非const访问map的key_type。到目前为止,只有libc++利用了这一点。如果您在上面的示例中检测A
,您将得到libc++:
start copy assignment
operator=(const A& a)
operator=(const A& a)
operator=(const A& a)
end copy assignment
libstdc++ (gcc-5.2.0)
start copy assignment
~A()
A(A const& a)
~A()
A(A const& a)
~A()
A(A const& a)
end copy assignment
为vs - 2015: start copy assignment
~A()
~A()
~A()
A(A const& a)
A(A const& a)
A(A const& a)
end copy assignment
我断言,当A
是像int
、std::vector
或std::string
这样的类型,或者包含这些常见std类型之一的类型时,赋值比先销毁后构造具有巨大的性能优势。赋值可以利用lhs中的现有容量,通常会导致简单的memcpy
,而不是先释放内存,然后再分配内存。
注意上面的~A()
可能意味着整个节点的释放,而不仅仅是A
(尽管不一定)。在任何情况下,libc++ map
拷贝赋值操作符都经过了高度优化以回收内存,并且对该优化的权限得到了c++ 11和更高标准的支持。联合技巧是实现这种优化的一种方法(不一定是可移植的)。
当分配器在移动赋值时不传播并且两个分配器比较不相等时,同样的代码对move赋值操作符进行了类似的优化:
叮当声/libc + +:
start move assignment
operator=(A&& a)
operator=(A&& a)
operator=(A&& a)
end move assignment
gcc-5.2.0
start move assignment
~A()
A(A const& a)
~A()
A(A const& a)
~A()
A(A const& a)
~A()
~A()
~A()
end move assignment
vs - 2015 start move assignment
~A()
~A()
~A()
A(A const& a)
A(A const& a)
A(A const& a)
end move assignment
当您使用自定义分配器时,可能需要将映射(及其内容)移动到新的资源池中。在这种情况下,此重载将提供对键的可移动访问:
__value_type(__value_type&& __v)
: __nc(std::move(__v.__nc)) {}
键是否被移走并不重要,因为接下来发生的事情是释放所有的节点。
注意,这种用法可能导致未定义的行为。通常不能写union的一个成员,然后读另一个成员。Clang和libc++可以这样做,只要它们能够在内部保证它不会导致问题(或错误诊断)。
他们这样做可能是因为没有好的替代方案。至少,我想不出一个。标准要求value_type::first_type
是真正合格的const
,所以即使是const_cast
也是不允许的。
在key_type
和mapped_type
都是标准布局的情况下,技巧是一致的,因此std::pair<key_type, mapped_type>
和std::pair<key_type const, mapped_type>
是布局兼容的,每个[类。mem)§9.2/16。这里看起来有点奇怪,因为该函数引用了联合的直接成员__cc
和__nc
,而让构造函数访问包含first
和second
的公共子序列。标准布局类型的要求有些限制,但是许多常见的键和值类型(例如,std::string
)可能满足它们。
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- 使用一个考虑到std::map中键值的滚动或换行的键
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 允许从 std::map 的密钥窃取资源?
- 带有 -stdlib=libc++ 的 clang++ 9.0.1 找不到<optional>
- 有没有办法对std::unordered_set、std::unrdered_map、std::set、std::map
- 将重物插入std::map
- C++17 - 使用自定义分配器的节点提取/重新插入 - 适用于 clang++/libc++,但不适用于 libstd
- 使用通用值初始化 std::map,不重复
- 仅包含可移动 std::map 的类的移动构造函数不起作用
- C++:当所有条目都保证是唯一时,替代 std::map
- 使用模板化的键类型定义 std::map,该键类型基于作为参数接收的函数
- 如果 KEY 是 std::list 或 std::vector 而不是值,那么 std::map 的默认行为是什么?
- 使用字符数组作为 Map 中的键
- 安装 libc++ 时出现问题
- C++如何创建 std::map
- 在libc++和libstdc++之间的std::map上使用std::find时的实现差异
- libc++ 对 std::map/set::equal_range 的实现给出了意想不到的结果
- 为什么libc++的map实现使用这种联合?