尝试为std :: string_view和std :: string在std :: unordered_set中提供解决方案,我正在尝试用
std :: unordered_map > 替换 std :: unordered_set (值是 std :: unique_ptr ,因为小字符串优化意味着 string 的基础数据的地址不总是作为 std :: move 的结果传输)。
我的原始测试代码(省略头文件)似乎有效:
using namespace std::literals;
int main(int argc, char **argv) {
std::unordered_map<std::string_view, std::unique_ptr<std::string>> mymap;
for (int i = 1; i < argc; ++i) {
auto to_insert = std::make_unique<std::string>(argv[i]);
mymap.try_emplace(*to_insert, std::move(to_insert));
}
for (auto&& entry : mymap) {
std::cout << entry.first << ": " << entry.second << std::endl;
}
std::cout << std::boolalpha << "\"this\" in map? " << (mymap.count("this") == 1) << std::endl;
std::cout << std::boolalpha << "\"this\"s in map? " << (mymap.count("this"s) == 1) << std::endl;
std::cout << std::boolalpha << "\"this\"sv in map? " << (mymap.count("this"sv) == 1) << std::endl;
return EXIT_SUCCESS;
}
我使用 g++
7.2.0 进行编译,编译命令为 g++ -O3 -std=c++17 -Wall -Wextra -Werror -flto -pedantic test_string_view.cpp -o test_string_view
,没有收到任何警告,然后运行程序,得到以下输出:
$ test_string_view this is a test this is a second test
second: second
test: test
a: a
this: this
is: is
"this" in map? true
"this"s in map? true
"this"sv in map? true
这正是我所预期的。
我的主要关注点在于是否:
mymap.try_emplace(*to_insert, std::move(to_insert));
已定义行为。 *to_insert
依赖于 to_insert
不被清空(通过移动构造存储在 map 中的 std::unique_ptr
)直到 string_view
构造完成。将考虑两个定义 try_emplace
:
try_emplace(const key_type& k, Args&&... args);
并且
try_emplace(key_type&& k, Args&&... args);
我不确定会选择哪个,但无论如何,似乎在调用try_emplace时,key_type都会被构建,而将mapped_type(“value”,尽管映射似乎使用value_type来引用组合键/值对)的参数转发,而不立即使用,这使代码定义。我的理解正确吗,还是这是未定义的行为?
我的担忧是其他类似的构造似乎明显是未定义的,但仍然可以工作,例如:
mymap.insert(std::make_pair<std::string_view,
std::unique_ptr<std::string>>(*to_insert,
std::move(to_insert)));
这段代码可以产生预期的输出,而类似的结构如下:
mymap.insert(std::make_pair(std::string_view(*to_insert),
std::unique_ptr<std::string>(std::move(to_insert))));
在运行时触发了Segmentation fault
,尽管它们都没有引起任何警告,并且两个构造似乎同样无序(工作的insert
中的无序隐式转换,在导致segfault的insert
中的无序显式转换),所以我不想说“try_emplace
对我有用,所以没问题。”
请注意,虽然这个问题与C++11: std::move() call on arguments' list类似,但它并不完全相同(这是使此处的std::make_pair
不安全的原因,但不一定适用于try_emplace
的基于转发的行为);在那个问题中,接收参数的函数接收std::unique_ptr
,立即触发构造,而try_emplace
接收用于转发而不是std::unique_ptr
的参数,因此虽然std::move
已经“发生”了(但还没有做任何事情),我认为我们是安全的,因为std::unique_ptr
是“稍后”构造的。
foo.frobnicate(*bar, std::move(bar))
会引起警觉。 - Justinstd::make_pair
,而不是直接使用std::pair
构造函数,它只是明确指定了模板类型(触发隐式构造),而不是显式构造,然后传递。那样不同样不安全吗?即使通过通用引用接收,它也必须构造正确的类型,只是将构造时间从“调用前”移动到“调用时”,但两者都是无序的,对吧?在“加载”参数并将其转换为所需类型之间没有序列点,对吧? - ShadowRangermake_pair
相同签名但空函数体的函数替换它,就不会进行移动操作。虽然没有更多的序列点,但是每个参数都会被初始化,然后是下一个参数...没有交错或类似的情况。 - Rakete1111*to_insert
构造string_view
,但只是将xvalue
引用传递给to_insert
,而不清空它;unique_ptr
已经是正确的类型。就像try_emplace
一样,string_view
是在调用std::make_pair
时立即创建的,而unique_ptr
直到后来才被移动构造。谢谢! - ShadowRanger