std::move 在转发参数而不是移动构造时在参数列表中安全吗?
Is std::move safe in an arguments list when the argument is forwarded, not move constructed?
试图为 提供解决方案,我正在尝试将 std::unordered_set<std::string>
替换为 std::unordered_map<std::string_view, std::unique_ptr<std::string>>
(值为 std::unique_ptr<std::string>
,因为小字符串优化会意味着 string
的基础数据的地址不会因为 std::move
) 而总是被传输。
我原来的测试代码,似乎有效,是(省略headers):
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
在构建 string_view
之前不会被清空(通过移动构造存储在地图中的 std::unique_ptr
)。将考虑的 try_emplace
的两个定义是:
try_emplace(const key_type& k, Args&&... args);
和
try_emplace(key_type&& k, Args&&... args);
我不确定会选择哪个,但无论哪种方式,似乎 key_type
都将作为调用 try_emplace
的一部分构建,而 mapped_type
的参数](“值”,虽然地图似乎使用 value_type
来指代组合的 key/value pair
)一起转发,而不是立即使用,这使得代码定义。我的解释是否正确,或者这是未定义的行为?
我担心的是,其他看起来绝对未定义的类似结构似乎仍然有效,例如:
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
,尽管其中 none 发出任何类型的警告,并且两个构造似乎同样未排序(工作中未排序的隐式转换 insert
,在段错误 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
是“稍后”构建的。
是的,您拨打 try_emplace
是绝对安全的。 std::move
实际上并没有移动任何东西,它只是将传递的变量转换为一个 xvalue。无论参数的初始化顺序如何,都不会移动任何内容,因为参数都是引用。引用直接绑定到对象,它们不调用任何构造函数。
如果您查看第二个代码段,您会发现 std::make_pair
也通过引用获取其参数,因此在这种情况下,除了在构造函数主体中外,也不会进行移动。
然而,您的第三个片段确实存在 UB 问题。差别很小,但是如果 make_pair
的参数是从左到右计算的,那么一个临时的 std::unique_ptr
对象将被初始化为 to_insert
的值。这意味着现在 to_insert
为空,因为移动确实发生了,因为您正在显式构建一个实际执行移动的对象。
试图为 std::unordered_set<std::string>
替换为 std::unordered_map<std::string_view, std::unique_ptr<std::string>>
(值为 std::unique_ptr<std::string>
,因为小字符串优化会意味着 string
的基础数据的地址不会因为 std::move
) 而总是被传输。
我原来的测试代码,似乎有效,是(省略headers):
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
在构建 string_view
之前不会被清空(通过移动构造存储在地图中的 std::unique_ptr
)。将考虑的 try_emplace
的两个定义是:
try_emplace(const key_type& k, Args&&... args);
和
try_emplace(key_type&& k, Args&&... args);
我不确定会选择哪个,但无论哪种方式,似乎 key_type
都将作为调用 try_emplace
的一部分构建,而 mapped_type
的参数](“值”,虽然地图似乎使用 value_type
来指代组合的 key/value pair
)一起转发,而不是立即使用,这使得代码定义。我的解释是否正确,或者这是未定义的行为?
我担心的是,其他看起来绝对未定义的类似结构似乎仍然有效,例如:
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
,尽管其中 none 发出任何类型的警告,并且两个构造似乎同样未排序(工作中未排序的隐式转换 insert
,在段错误 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
是“稍后”构建的。
是的,您拨打 try_emplace
是绝对安全的。 std::move
实际上并没有移动任何东西,它只是将传递的变量转换为一个 xvalue。无论参数的初始化顺序如何,都不会移动任何内容,因为参数都是引用。引用直接绑定到对象,它们不调用任何构造函数。
如果您查看第二个代码段,您会发现 std::make_pair
也通过引用获取其参数,因此在这种情况下,除了在构造函数主体中外,也不会进行移动。
然而,您的第三个片段确实存在 UB 问题。差别很小,但是如果 make_pair
的参数是从左到右计算的,那么一个临时的 std::unique_ptr
对象将被初始化为 to_insert
的值。这意味着现在 to_insert
为空,因为移动确实发生了,因为您正在显式构建一个实际执行移动的对象。