为什么 std::map 的移动构造函数不是 noexcept?
Why is std::map's move constructor not noexcept?
正如cppreference.com所说,
Maps are usually implemented as red-black trees.
所以移动一个std::map
只是将指针移动到根node
+其他信息如大小。为什么 std::map
的移动构造函数没有标记为 noexcept
?
这是因为我无法说服所有的实现者进入 map
可以进入的无资源状态。例如,一个实现需要有一个端节点指向,即使在默认构造状态下也是如此。允许但不要求实现将该端节点放在堆上。
A moved-from map must be in a valid state. 即当 end()
被调用时,移出的 map
必须有一个结束节点指向。在 move 构造之前,map
中存在一个您要移动的结束节点。在 move 构造之后,必须存在两个结束节点:一个在新 map
中,一个在 moved-from `map.
中
如果结束节点进入堆,这意味着移动构造函数不转移结束节点的所有权,因此必须为新的`map分配一个新的结束节点。或者确实转移了结束节点,但必须分配一个新的留在移动源中。
如果结束节点嵌入在 map
数据结构本身中,那么它永远不需要在堆上分配。当 map
被构建时,它会自动 "allocated on the stack"。
允许实现 map
移动构造函数 noexcept
如果他们愿意,他们只是不需要。
这是我几年前的实现中的一个 survey of the noexcept-state of the default constructor, move constructor and move assignment operator of the containers。本次调查假设每个容器 std::allocator
。我只是抽查了 map
,结果没有改变。
如果您想 运行 自己进行此调查,请使用代码:
#include "type_name.h"
#include <iostream>
#include <type_traits>
#include <deque>
#include <forward_list>
#include <list>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
template <class C>
void
report()
{
using namespace std;
const auto name = type_name<C>();
if (is_nothrow_default_constructible<C>::value)
std::cout << name << " is noexcept default constructible\n";
else
std::cout << name << " is NOT noexcept default constructible\n";
if (is_nothrow_move_constructible<C>::value)
std::cout << name << " is noexcept move constructible\n";
else
std::cout << name << " is NOT noexcept move constructible\n";
if (is_nothrow_move_assignable<C>::value)
std::cout << name << " is noexcept move assignable\n\n";
else
std::cout << name << " is NOT noexcept move assignable\n\n";
}
int
main()
{
using namespace std;
report<deque<int>>();
report<forward_list<int>>();
report<list<int>>();
report<vector<int>>();
report<string>();
report<map<int, int>>();
report<set<int>>();
report<unordered_map<int, int>>();
report<unordered_set<int>>();
}
其中 "type_name.h"
来自 this answer.
正如cppreference.com所说,
Maps are usually implemented as red-black trees.
所以移动一个std::map
只是将指针移动到根node
+其他信息如大小。为什么 std::map
的移动构造函数没有标记为 noexcept
?
这是因为我无法说服所有的实现者进入 map
可以进入的无资源状态。例如,一个实现需要有一个端节点指向,即使在默认构造状态下也是如此。允许但不要求实现将该端节点放在堆上。
A moved-from map must be in a valid state. 即当 end()
被调用时,移出的 map
必须有一个结束节点指向。在 move 构造之前,map
中存在一个您要移动的结束节点。在 move 构造之后,必须存在两个结束节点:一个在新 map
中,一个在 moved-from `map.
如果结束节点进入堆,这意味着移动构造函数不转移结束节点的所有权,因此必须为新的`map分配一个新的结束节点。或者确实转移了结束节点,但必须分配一个新的留在移动源中。
如果结束节点嵌入在 map
数据结构本身中,那么它永远不需要在堆上分配。当 map
被构建时,它会自动 "allocated on the stack"。
允许实现 map
移动构造函数 noexcept
如果他们愿意,他们只是不需要。
这是我几年前的实现中的一个 survey of the noexcept-state of the default constructor, move constructor and move assignment operator of the containers。本次调查假设每个容器 std::allocator
。我只是抽查了 map
,结果没有改变。
如果您想 运行 自己进行此调查,请使用代码:
#include "type_name.h"
#include <iostream>
#include <type_traits>
#include <deque>
#include <forward_list>
#include <list>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
template <class C>
void
report()
{
using namespace std;
const auto name = type_name<C>();
if (is_nothrow_default_constructible<C>::value)
std::cout << name << " is noexcept default constructible\n";
else
std::cout << name << " is NOT noexcept default constructible\n";
if (is_nothrow_move_constructible<C>::value)
std::cout << name << " is noexcept move constructible\n";
else
std::cout << name << " is NOT noexcept move constructible\n";
if (is_nothrow_move_assignable<C>::value)
std::cout << name << " is noexcept move assignable\n\n";
else
std::cout << name << " is NOT noexcept move assignable\n\n";
}
int
main()
{
using namespace std;
report<deque<int>>();
report<forward_list<int>>();
report<list<int>>();
report<vector<int>>();
report<string>();
report<map<int, int>>();
report<set<int>>();
report<unordered_map<int, int>>();
report<unordered_set<int>>();
}
其中 "type_name.h"
来自 this answer.