当转发参数时,std :: move在参数列表中是否安全,而不是移动构造?

Sha*_*ger 5 c++ move-semantics string-view c++17

试图提供解决方案的std ::性病string_view和的std :: string :: unordered_set,我与更换玩弄std::unordered_set<std::string>std::unordered_map<std::string_view, std::unique_ptr<std::string>>(该值std::unique_ptr<std::string>,因为小串的优化将意味着该地址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;
}
Run Code Online (Sandbox Code Playgroud)

我用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
Run Code Online (Sandbox Code Playgroud)

这是我的预期.

我主要担心的是:

        mymap.try_emplace(*to_insert, std::move(to_insert));
Run Code Online (Sandbox Code Playgroud)

定义了行为.的*to_insert依赖于to_insert不被排空(由移动构建std::unique_ptr存储在映射)后直到string_view被构造.try_emplace将考虑的两个定义是:

try_emplace(const key_type& k, Args&&... args);
Run Code Online (Sandbox Code Playgroud)

try_emplace(key_type&& k, Args&&... args);
Run Code Online (Sandbox Code Playgroud)

我不确定会选择哪个,但无论哪种方式,它似乎key_type都会被构造为调用的一部分try_emplace,而制作mapped_type("值"的参数,虽然map似乎value_type用于指代组合的键/值pair转发,而不是立即使用,这使代码定义.我在这种解释中是正确的,还是这种未定义的行为?

我担心的是,看起来绝对未定义的其他类似结构似乎仍然有效,例如:

mymap.insert(std::make_pair<std::string_view,
                            std::unique_ptr<std::string>>(*to_insert,
                                                          std::move(to_insert)));
Run Code Online (Sandbox Code Playgroud)

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

mymap.insert(std::make_pair(std::string_view(*to_insert),
                            std::unique_ptr<std::string>(std::move(to_insert))));
Run Code Online (Sandbox Code Playgroud)

Segmentation fault在运行时触发a ,尽管它们都没有引发任何类型的警告,并且这两个构造似乎同样没有序列(insert在segfaulting中工作的,无序的显式转换中没有序列的隐式转换insert),所以我不想说" try_emplace为我工作,所以没关系."

请注意,虽然这个问题类似于C++ 11:std :: move()调用参数列表,但它并不是一个重复的(它可能会使std::make_pair这里不安全,但不一定适用于try_emplace基于转发的行为) ; 在那个问题中,接收参数的函数接收std::unique_ptr,立即触发构造,同时try_emplace接收转发参数,而不是std::unique_ptr,所以当" std::move已经发生"(但尚未完成)时,我认为我们是安全的,因为它std::unique_ptr是"以后构建的" ".

Rak*_*111 4

是的,您拨打的电话try_emplace是绝对安全的。std::move实际上并没有移动任何东西,它只是将传递的变量转换为 xvalue。无论参数初始化的顺序如何,都不会移动任何内容,因为参数都是引用。引用直接绑定到对象,它们不调用任何构造函数。

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

然而,你的第三个片段确实存在 UB 的问题。区别很微妙,但如果 的参数make_pair从左到右求值,则临时std::unique_ptr对象将使用 的移出值进行初始化to_insert。这意味着现在to_insert为 null,因为移动实际上发生了,因为您正在显式构造实际执行移动的对象。

  • 虽然这是事实,但我仍然建议要非常小心。您必须知道要调用的函数的签名,才能知道它的所有参数都是引用,这样才安全。如果作者后来将参数更改为按值,则这将不再安全。由于对函数的签名一无所知,“foo.frobnicate(*bar, std::move(bar))”会引发危险信号。 (4认同)