C++中的unique_ptr和map

25

我正在尝试在 map 中使用 C++0xunique_ptr,如下所示:

// compile with `g++ main.cpp -std=gnu++0x`

#include <string.h>    
#include <map>
#include <memory>

using namespace std;

struct Foo {
    char *str;    
    Foo(char const *str_): str(strdup(str_)) {}
};

int main(void) {
    typedef std::map<int, unique_ptr<Foo>> Bar;
    Bar bar;
    auto a = bar.insert(Bar::value_type(1, new Foo("one")));
    return 0;
}

然而,GCC给我以下错误(简化了一下,我认为这是相关的部分,请在您自己的C++编译器上进行测试):

main.cpp:19:   instantiated from here
/usr/include/c++/4.4/bits/unique_ptr.h:214: error: deleted function ‘std::unique_ptr::unique_ptr(const std::unique_ptr&) [with _Tp = Foo, _Tp_Deleter = std::default_delete]’
/usr/include/c++/4.4/bits/stl_pair.h:68: error: used here

我真的不确定我做错了什么,这在MSVC上运行正常。我找到了非常相似的问题,看起来类似,但它们的解决方法对我不起作用。

matt@stanley:/media/data/src/c++0x-test$ gcc --version
gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3

1
不是答案,但Foo应该有一个析构函数来释放str,因为strdup会分配内存并将str_复制到该内存中。 - Lou Franco
5
无论何时官方发布,它都是C++0x。正如Stroustrup所说,把x想象成十六进制即可 :) - Armen Tsirunyan
@Loy Franco:是的,我知道,下一步就是实现它,但我从来没有做到这一步(这只是我遇到的一个更大问题的简化提取)。 - Matt Joiner
你应该添加一个析构函数 Foo::~Foo,释放内存并显式禁用任何复制。 - sellibitze
4个回答

28

首先:你的Foo类是std::string的一个非常糟糕的近似。我预测会有很多内存泄漏。(搜索“三法则”)

其次:出于安全原因,指针不能隐式转换为unique_ptr对象。但是,模板化的pair构造函数要求参数能够隐式转换为相应的值类型。GCC似乎允许这样做,但这是一个错误。您必须手动创建unique_ptr对象。不幸的是,C++0x草案缺少以下非常有用的函数模板,它可以简化临时unique_ptr对象的创建。它也是异常安全的:

template<class T, class...Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    std::unique_ptr<T> ret (new T(std::forward<Args>(args)...));
    return ret;
}

此外,让我们使用新的emplace函数而不是insert

auto a = bar.emplace(1,make_unique<Foo>("one"));
< p > emplace 将两个参数转发到相应的pair构造函数中。

你目睹的实际问题是,尽管unique_ptr对象仅“可移动”,但pair构造函数仍然尝试复制它。看起来GCC对C++0x的支持还不够好,无法正确处理这种情况。据我所知,根据当前标准草案(N3126),我上面的代码应该可以工作。

编辑:我刚刚尝试了以下解决方法,使用实验性的C++0x模式下的GCC 4.5.1:

map<int,unique_ptr<int> > themap;
themap[42].reset(new int(1729));

这也应该在即将推出的标准中起作用,但GCC也拒绝了它。看起来你必须等待GCC支持在映射和多映射中使用unique_ptr。


@Matt:嗯,这个问题我也无能为力。在映射类型中使用std::unique_ptr应该是可以的,但GCC目前还无法处理。你可以提交一个错误报告。 - sellibitze
1
感谢您提供出色的答案。当GCC添加支持后,我将能够测试并给予+1! - Matt Joiner
1
我也遇到了这个问题。感谢您详细的说明,省去了我很多烦恼。 - Neil G
这似乎也是在 Clang 3.6.2 中的一个问题。 - dashesy

6

有关的代码在 GCC 4.6.1 上运行,只需进行一些小修改(添加一个显式转换为 unique_ptr<Foo>)。 - Rotsor

1

我刚刚在gcc 4.6.0中尝试了unique_ptr。这里有一些(非常丑陋的)代码,可以正确运行:

std::map<int, std::unique_ptr<int> > table;
table.insert(std::pair<int, std::unique_ptr<int>>(15, std::unique_ptr<int>(new int(42))));
table[2] = std::move(table[15]);
for (auto& i : table)
{
    if (i.second.get())
        printf("%d\n", *i.second);
    else
    {
        printf("empty\n");
        i.second.reset(new int(12));
    }
}
printf("%d\n", *table[15]);

输出为 42,空,12 - 所以显然它是有效的。就我所知,唯一的“问题”似乎就是那个糟糕的插入命令。在gcc 4.6.0中没有实现“emplace”。

+1 至少插入和对转换构造函数似乎按预期工作(转换,因为value_typepair<const int,....)。但是仍存在与异常安全性有关的轻微问题。通常,动态分配对象并使用该指针初始化unique_ptr应该是连续的操作。但是C ++不保证在哪个顺序下子表达式被评估。这就是为什么我使用make_unique作为函数调用的原因。这样,就有两个额外的序列点。 - sellibitze

0
请尝试使用 map 的 emplace 方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接