转发参数而不是移动构造时,std :: move在参数列表中安全吗? [英] Is std::move safe in an arguments list when the argument is forwarded, not move constructed?

查看:99
本文介绍了转发参数而不是移动构造时,std :: move在参数列表中安全吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

尝试为 std :: string_view和std :: string in std :: unordered_set 提供解决方案,我是m尝试将std::unordered_set<std::string>替换为std::unordered_map<std::string_view, std::unique_ptr<std::string>>(值是std::unique_ptr<std::string>,因为小的字符串优化将意味着string的基础数据的地址将不会由于std::move而总是被传输

Trying to provide a solution to std::string_view and std::string in std::unordered_set, I'm playing around with replacing std::unordered_set<std::string> with std::unordered_map<std::string_view, std::unique_ptr<std::string>> (the value is std::unique_ptr<std::string> because the small string optimization would mean that the address of the string's underlying data will not always be transferred as a result of std::move.

我的原始测试代码似乎可行,(省略标题):

My original test code, that seems to work, is (omitting 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,没有收到任何警告,然后运行,得到以下输出:

I compile with g++ 7.2.0, compile line is g++ -O3 -std=c++17 -Wall -Wextra -Werror -flto -pedantic test_string_view.cpp -o test_string_view receiving no warnings of any kind, then run, getting the following output:

$ 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

这是我所期望的.

我在这里主要担心的是:

My main concern here is whether:

        mymap.try_emplace(*to_insert, std::move(to_insert));

已定义行为. *to_insert依赖于to_insert直到构造string_view之后才被清空(通过移动构造存储在映射中的std::unique_ptr).将考虑的try_emplace的两个定义是:

has defined behavior. The *to_insert relies on to_insert not being emptied (by move constructing the std::unique_ptr stored in the map) until after the string_view is constructed. The two definitions of try_emplace that would be considered are:

try_emplace(const key_type& k, Args&&... args);

try_emplace(key_type&& k, Args&&... args);

我不确定会选择哪个,但是无论哪种方式,似乎key_type都将被构造为调用try_emplace的一部分,而构成mapped_type的参数(尽管是值"映射似乎使用value_type来引用组合的键/值pair),并且不会立即使用,这使代码得以定义.我对这种解释是正确的,还是这种不确定的行为?

I'm not sure which would be chosen, but either way, it seems like key_type would be constructed as part of calling try_emplace, while the arguments to make the mapped_type (the "value", though maps seem to use value_type to refer to the combined key/value pair) are forwarded along, and not used immediately, which makes the code defined. Am I correct in this interpretation, or is this undefined behavior?

我担心的是,其他似乎绝对不确定的类似构造似乎仍然有效,例如:

My concern is that other, similar constructions that seem definitely undefined still seem to work, e.g.:

mymap.insert(std::make_pair<std::string_view,
                            std::unique_ptr<std::string>>(*to_insert,
                                                          std::move(to_insert)));

产生预期的输出,而类似的结构例如:

produces the expected output, while similar constructs like:

mymap.insert(std::make_pair(std::string_view(*to_insert),
                            std::unique_ptr<std::string>(std::move(to_insert))));

在运行时触发Segmentation fault,尽管它们都未发出任何警告,而且两个结构似乎均未排序(工作的insert中未排序的隐式转换,segfaulting的insert中未排序的显式转换),因此我不想说"try_emplace为我工作,所以没关系."

trigger a Segmentation fault at runtime, despite none of them raising warnings of any kind, and both constructs seemingly equally unsequenced (unsequenced implicit conversions in the working insert, unsequenced explicit conversion in the segfaulting insert), so I don't want to say "try_emplace worked for me, so it's okay."

请注意,尽管此问题类似于 C ++ 11:std :: move()调用参数列表,它不是一个完全相同的副本(这可能会使std::make_pair在此不安全,但不一定适用于try_emplace基于转发的行为);在该问题中,接收自变量的函数将接收std::unique_ptr,立即触发构造,而try_emplace正在接收用于转发的自变量,而不是std::unique_ptr,因此,当std::move已发生"(但未执行任何操作)时,我认为我们很安全,因为std::unique_ptr是以后"构造的.

Note that while this question is similar to C++11: std::move() call on arguments' list, it's not quite a duplicate (it's what presumably makes the std::make_pair here unsafe, but not necessarily applicable to try_emplace's forwarding based behavior); in that question, the function receiving the arguments receives std::unique_ptr, triggering construction immediately, while try_emplace is receiving arguments for forwarding, not std::unique_ptr, so while the std::move has "occurred" (but done nothing yet), I think we're safe since the std::unique_ptr is constructed "later".

推荐答案

是的,您对try_emplace的调用非常安全. std::move实际上不移动任何东西,它只是将传递的变量强制转换为xvalue.无论参数以什么顺序初始化,都不会移动任何内容,因为参数都是引用.引用直接绑定到对象,它们不调用任何构造函数.

Yes, your call to try_emplace is perfectly safe. std::move actually doesn't move anything, it just casts the passed variable to an xvalue. No matter what order the arguments are initialized, nothing will ever be moved because the parameters are all references. References bind directly to objects, they do not call any constructors.

如果您查看第二个片段,您会发现std::make_pair也通过引用获取其参数,因此在这种情况下,除了在构造函数主体中,也不会进行任何移动.

If you look at your second snippet, you'll see that std::make_pair also takes its parameters by reference, so in that case too a move will not be made except in the constructor body.

您的第三个代码段确实存在UB问题.区别是细微的,但是如果从左到右评估make_pair的参数,则将使用从to_insert的值移动来初始化临时的std::unique_ptr对象.这意味着to_insert为null,因为实际上发生了移动,因为您正在显式构造一个实际执行移动的对象.

Your third snippet however does have the problem of UB. The difference is subtle, but if the arguments of make_pair are evaluated left-to-right, then a temporary std::unique_ptr object will get initialized with the moved from value of to_insert. This means that now, to_insert is null because a move actually happened because you are explicitly constructing an object that actually performs the move.

这篇关于转发参数而不是移动构造时,std :: move在参数列表中安全吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆