1

I have following code that has vector of a class that has some member declared as unique_ptr.

struct Container
{
    struct Nested{
        std::unique_ptr<Container> node;

        Nested(std::unique_ptr<Container> t) : node(std::move(t)) {}
        Nested(const Nested& t) { node = std::move(t.node); };
    };

    std::vector<Nested> edges;
};

typedef std::unique_ptr<Container> UCont;
typedef Container::Nested Nested;


int main()
{
    std::unique_ptr<Container> object = UCont(new Container{{
                                    Nested(UCont(new Container{{}})),
                                    Nested(UCont(new Container{{}})),
                                    Nested(UCont(new Container{{}}))
                                }});
}

Now compiling this is giving the following error:

..\00.UniquePtrVector.cpp: In copy constructor 'Container::Nested::Nested(const Container::Nested&)':
..\00.UniquePtrVector.cpp:20:35: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Container; _Dp = std::default_delete<Container>]'
    Nested(const Nested& t) { node = std::move(t.node); };
                                   ^
In file included from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\locale_conv.h:41:0,
                 from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\locale:43,
                 from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\iomanip:43,
                 from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\mingw32\bits\stdc++.h:71,
                 from ..\00.UniquePtrVector.cpp:10:
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\unique_ptr.h:357:19: note: declared here
       unique_ptr& operator=(const unique_ptr&) = delete;

I am not sure how to fix this error. I guess removing the copy constructor is not an option either. Any help?

EDIT: Changing to

Nested(std::unique_ptr<Container>&& t) : node(std::move(t)) {}
Nested(Nested&& t) : node(std::move(t.node)) {}
Nested(const Nested& t) =delete;

is also giving error:

In file included from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_tempbuf.h:60:0,
                 from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_algo.h:62,
                 from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\algorithm:62,
                 from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\mingw32\bits\stdc++.h:64,
                 from ..\00.UniquePtrVector.cpp:10:
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_construct.h: In instantiation of 'void std::_Construct(_T1*, _Args&& ...) [with _T1 = Container::Nested; _Args = {const Container::Nested&}]':
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_uninitialized.h:75:18:   required from 'static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const Container::Nested*; _ForwardIterator = Container::Nested*; bool _TrivialValueTypes = false]'
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_uninitialized.h:126:15:   required from '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const Container::Nested*; _ForwardIterator = Container::Nested*]'
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_uninitialized.h:281:37:   required from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = const Container::Nested*; _ForwardIterator = Container::Nested*; _Tp = Container::Nested]'
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_vector.h:1290:33:   required from 'void std::vector<_Tp, _Alloc>::_M_range_initialize(_ForwardIterator, _ForwardIterator, std::forward_iterator_tag) [with _ForwardIterator = const Container::Nested*; _Tp = Container::Nested; _Alloc = std::allocator<Container::Nested>]'
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_vector.h:377:21:   required from 'std::vector<_Tp, _Alloc>::vector(std::initializer_list<_Tp>, const allocator_type&) [with _Tp = Container::Nested; _Alloc = std::allocator<Container::Nested>; std::vector<_Tp, _Alloc>::allocator_type = std::allocator<Container::Nested>]'
..\00.UniquePtrVector.cpp:36:11:   required from here
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_construct.h:75:7: error: use of deleted function 'Container::Nested::Nested(const Container::Nested&)'
     { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
       ^
..\00.UniquePtrVector.cpp:20:11: note: declared here
           Nested(const Nested& t) =delete;
           ^
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
saha
  • 97
  • 8

3 Answers3

3

When you move t.node in your copy constructor, t.node needs to change. But t is const, so the move is invalid. unique_ptr cannot be copy constructed, thus struct Nested cannot be copy constructed either.

To make it work, you'll need to supply a move constructor and delete the copy constructor. Something like this:

    struct Nested{
        std::unique_ptr<Container> node;
        Nested(std::unique_ptr<Container>&& t) : node(std::move(t)) {}
        Nested(Nested&& t) : node(std::move(t.node)) {}
        Nested(const Nested& t) =delete;
    };
kjpus
  • 509
  • 4
  • 8
  • I understood that. But how to code this kind of data model. – saha Apr 18 '18 at 02:28
  • Changing to what you shown is also giving error. I have updated the question with new error. – saha Apr 18 '18 at 11:24
  • 1
    @saha That should likely be a new question. We generally don't like changes to a question that make a correct answer to the problem shown no longer complete. – aschepler Apr 18 '18 at 11:32
  • @kjpus why not define the move constructor as `=default`? – Jonathan Wakely Apr 18 '18 at 11:36
  • @aschepler I tried putting a comment in the answer itself, as answer was not working for me. But the error was too big for a comment. So I had to put in the question. – saha Apr 18 '18 at 14:46
2

tl;dr : Consider using the rule of zero

I would be wary of explicitly specifying all those constructors in your class. The fact that unique_ptr's can't be copied will get you in all sorts of trouble when you do things the way you tried to. Here's what happens when, instead, you don't specify any constructors:

#include <vector>
#include <memory>

struct Container
{
    struct Nested{
        std::unique_ptr<Container> node;
//
//        Nested(std::unique_ptr<Container> t) : node(std::move(t)) {}
//        Nested(const Nested& t) { node = std::move(t.node); };
    };

    std::vector<Nested> edges;
};

typedef std::unique_ptr<Container> UCont;
typedef Container::Nested Nested;


int main()
{
    auto c1 = new Container{};
    auto c2 = new Container{};
    auto c3 = new Container{};
    std::unique_ptr<Container> u1 {c1};
    std::unique_ptr<Container> u2 {c2};
    std::unique_ptr<Container> u3 {c3};
    Nested n1 {std::move(u1)};
    Nested n2 {std::move(u2)};
    Nested n3 {std::move(u3)};
    auto v = std::vector<Nested>{3};
    v.push_back(std::move(n1));
    v.push_back(std::move(n2));
    v.push_back(std::move(n3));
    auto c5 = new Container { std::move(v) };
    std::unique_ptr<Container> object = UCont(std::move(c5));
}

I've broken everything down to shorter statements for clarity (mostly).

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • May as well go all the way `struct Container { std::vector> edges; };` – Caleth Apr 18 '18 at 13:56
  • @Caleth: I'm guessing OP has his/her reasons for the choice of class hierarchy (and probably cut out some code for the MCVE), so I didn't want to remove more than necessary to make my point. – einpoklum Apr 18 '18 at 22:51
  • This solution works but the main point was unique_ptr cannot be used in initializer list. Rule of zero was good. Thanks for that. But even with no constructor if you write main like I wrote, it will not work. I found it from https://stackoverflow.com/questions/9618268/initializing-container-of-unique-ptrs-from-initializer-list-fails-with-gcc-4-7 – saha Apr 19 '18 at 14:11
  • @saha: You make a valid point. Perhaps you might want to ask a question about that specifically, regardless of the other details of your class hierarchy. – einpoklum Apr 19 '18 at 16:59
  • @einpoklum The class hierarchy is also important here. It is not that only `unique_ptr` cannot be used in initializer list. Any object that has a `unique_ptr` inside its data model can also not be used. – saha Apr 20 '18 at 01:04
  • @saha: I realize that, which is why I suggested an _additional_ question... – einpoklum Apr 20 '18 at 08:18
0

The root cause of the error I was getting is due to use of unique_ptr inside the object that is used in intializer list. Similar to vector of simple unique_ptr (as in here Initializing container of unique_ptrs from initializer list fails with GCC 4.7 ) an object that also has unique_ptr in its data model can also NOT be used in initializer list.

Similar to the other link, cause is same; initializer list always performs copies and unique_ptr cannot be copied. So we have to use emplace_back/push_back.

So even with constructor in place the following solution works

struct Container
{
    struct Nested{
            std::unique_ptr<Container> node;

            Nested(): node(nullptr) {}
            Nested(std::unique_ptr<Container> t) : node(std::move(t)) {}
        };

    std::vector<Nested> edges;
};

typedef std::unique_ptr<Container> UCont;
typedef Container::Nested Nested;


int main()
{
        auto v = std::vector<Nested>{3};
        v.push_back(std::move(Nested(std::move(std::unique_ptr<Container>(new Container{})))));
        v.push_back(std::move(Nested(std::move(std::unique_ptr<Container>(new Container{})))));
        v.push_back(std::move(Nested(std::move(std::unique_ptr<Container>(new Container{})))));
        std::unique_ptr<Container> object = UCont(new Container { std::move(v) });

}
saha
  • 97
  • 8