5

试图为std::unordered_set 中的 std::string_view 和 std::string提供解决方案,我正在玩替换std::unordered_set<std::string>with 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;
}

我用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才被清空(通过移动构造std::unique_ptr存储在地图中)string_view。将考虑的两个定义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指代组合的键/值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))));

在运行时触发 a Segmentation fault,尽管它们都没有发出任何类型的警告,并且两个构造似乎都同样无序(工作中的无序隐insert式转换, segfaulting 中的无序显式转换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“稍后”构造”。

4

1 回答 1

4

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

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

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

于 2018-08-08T17:23:40.470 回答