24
votes

I'm trying to use the C++0x unique_ptr class inside a map like so:

// 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;
}

However GCC gives me the following error (shortened, I think this is the relevant part, please test on your own C++ compiler):

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

I'm really not sure what I've done wrong, this works on MSVC. I have found very similar questions, that seem alike, however their solutions do not work for me.

matt@stanley:/media/data/src/c++0x-test$ gcc --version
gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3
4
Not the answer, but Foo should have a destructor that frees str because strdup mallocs memory and copies str_ into that memory.Lou Franco
It's C++0x no matter when it is officially published. As Stroustrup put it, think of x as a hexadecimal :)Armen Tsirunyan
@Armen: Thanks for that happy thought :DMatt Joiner
@Loy Franco: Yeah I know, that would be next to implement, but I never made it this far (it's a simplified extract of a larger problem I'm having).Matt Joiner
You should add a destructor Foo::~Foo that frees the memory and explicitly disable any copying.sellibitze

4 Answers

28
votes

First: Your class Foo is really a very bad approximation of std::string. I predict lots of memory leaks. (Search for "rule of three".)

Second: A pointer is not implicitly convertible to a unique_ptr object (for safety reasons). But the templated pair constructor requires the arguments to be implicitly convertible to the respective value types. GCC seems to allow this but it is a bug. You have to create the unique_ptr object manually. Unfortunately the C++0x draft lacks the following, very useful function template that eases the creation of temporary unique_ptr objects. It's also exception-safe:

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;
}

In addition, let's use the new emplace function instead of insert:

auto a = bar.emplace(1,make_unique<Foo>("one"));

emplace forwards both arguments to the corresponding pair constructor.

The actual problem you witnessed is that the pair constructor tried to copy a unique_ptr even though such an object is only "movable". It seems that GCC's C++0x support is not yet good enough to handle this situation correctly. From what I can tell, my line from above should work according to the current standard draft (N3126).

Edit: I just tried the following as a workaround using GCC 4.5.1 in experimental C++0x mode:

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

This is also supposed to work in the upcoming standard but GCC rejects it as well. Looks like you have to wait for GCC to support unique_ptr in maps and multimaps.

6
votes

I don't believe it's possible to correctly use unique_ptr in a map::insert yet. Here's the GCC bug for it:

Bug 44436 - [C++0x] Implement insert(&&) and emplace* in associative and unordered containers

It looks like it may be fixed for GCC 4.6, but it won't build from SVN on vanilla Ubuntu 10.04.1 to confirm.

Update0

This is not working as of GCC svn r165422 (4.6.0).

1
votes

I've just been tinkering with unique_ptr in gcc 4.6.0. Here's some (very ugly) code which works correctly:

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]);

The output is 42, empty, 12 - so apparently it works. As far as I can tell, the only 'problem' is that awful looking insert command. 'emplace' isn't implemented in gcc 4.6.0

0
votes

Try using emplace method of map.