插入包含 unique_ptr 的对象时,c++ map insert 无法编译

c++ map insert fails to compile when inserting an object that contains a unique_ptr

我在(编译时,gcc 17)在包含 unique_ptr 的 std::map 中插入对象时遇到问题。如果我使用常规指针,它会编译(如果我取出复制构造函数!)。 这是包含 unique_ptr 的结构。使用构造函数、复制构造函数等,到处都有 std::move 语义。有人以前见过这个问题吗?

#include <utility>      // std::pair, std::make_pair
#include <string>       // std::string
#include <iostream>     // std::cout
#include <map>
#include <memory>

struct Struct1
{
    Struct1(std::unique_ptr<std::string> pStr)
        : pStr_(std::move(pStr)){}
   ~Struct1(){}
    Struct1(Struct1& other)
        : pStr_(std::move(other.pStr_)){}
    std::unique_ptr<std::string> pStr_;
};

这是主要的。无论我尝试什么,我都会得到 insert

的“无匹配功能”
int main () {
    std::string s("key");
    std::unique_ptr<std::string> pStr = std::make_unique<std::string>("Hello");

    std::map<std::string, Struct1>list;
    Struct1 ste(std::move(pStr));
    std::pair<std::string, Struct1> p(s,ste); // copy into a pair
    // std::pair<std::string, Struct1> p(s,std::move(ste)); // copy into a pair
    // auto p = std::make_pair(s, std::move(ste));
    // auto p = std::make_pair(s, ste);
    // list.insert(std::move(p));
    list.insert(p);
Struct1(Struct1& other)

按照语言标准,它是复制构造函数;你在这里所做的(我不知道是拼写错误还是故意的)是 auto_ptr 风格的 neither-move-nor-copy-semantics。虽然完成后,复制分配是 auto-generated,而移动操作被完全删除(除非手动定义)。

你不想要那个吗?


#include <utility>
#include <string>      
#include <iostream>    
#include <map>
#include <memory>

struct Struct1
{
    Struct1(std::unique_ptr<std::string> pStr)
        : pStr_(std::move(pStr)){}
   ~Struct1(){}
    Struct1(Struct1&& other) //double! ampersand
        : pStr_(std::move(other.pStr_)){}
    std::unique_ptr<std::string> pStr_;
};

int main () {
    std::string s("key");
    std::unique_ptr<std::string> pStr = std::make_unique<std::string>("Hello");

    std::map<std::string, Struct1>list;
    Struct1 ste(std::move(pStr));
    std::pair<std::string, Struct1> p{s,std::move(ste)}; //note the move 
    list.insert(std::move(p)); //and here
}

https://godbolt.org/z/fs44j69ja

当您选择加入 structs 时,除非确实需要,否则您可以避免创建自己的构造函数。相反,Struct1 可以简单地是:

struct Struct1
{
    std::unique_ptr<std::string> pStr_;
};

所有 copy/move 构造函数和赋值运算符将尽可能自动生成。但是,由于结构中有一个 unique_ptr,它没有复制构造函数或赋值运算符,因此不会为您生成这些,无论如何您都不需要它们。


现在剩下的就很简单了,只要你记得正确地调用你的对象 move:

std::string s("key");
std::map<std::string, Struct1> list;

// prefer using `auto` when the type can already be deduced from `make_unique`
auto pStr = std::make_unique<std::string>("Hello");

// You must move `pStr` to construct `ste` since `pStr` is a `unique_ptr`.
// Also note you are using bracket `{}`, instead of parentheses `()` to create `Struct1`
Struct1 ste{std::move(pStr)};
// With C++20, parentheses is also allowed:
Struct1 ste(std::move(pStr));

// Similarly, you must move `ste` since it contents a `unique_ptr` in it
// Also note the type of the pair, the Key must be `const`
std::pair<const std::string, Struct1> p(s, std::move(ste));

// Once again, you must move `p` for the same reason
list.insert(std::move(p));

此外,还有两个步骤可以缩短:

std::string s("key");
std::map<std::string, Struct1> list;

// You can create `ste` directly without creating `pStr` first.
// This will also reduce the chance for accidentally using `pStr` again later.
Struct1 ste{std::make_unique<std::string>("Hello")};

// You can then simply emplace `ste` into `list`, or use the subscript operator`[]` without creating the `pair` first
list.emplace(s, std::move(ste));
// or
list[s] = std::move(ste);

您甚至可以更积极地减少代码:

std::string s("key");
std::map<std::string, Struct1> list;

list[s] = {std::make_unique<std::string>("Hello")};
// or since C++20
list.emplace(s, std::make_unique<std::string>("Hello"));