C++0x: unique_ptr and std::move

Discussion in 'C++' started by Micha³ 'Khorne' Rzechonek, Jan 28, 2009.

  1. Hello,

    I wanted o understand how rvalue references work, so I took GCC 4.3
    with -std=c++0x flag and wrote code below.

    What I don't understand is why 2nd assertion fails and move ctor is
    not
    called. Please enlighten me :)

    Side question: does source() function look all right?

    #include <iostream>
    #include <cassert>

    using std::cout;
    using std::endl;
    using std::move;

    template<typename T>
    class unique_ptr {
    public:
    explicit unique_ptr(T *&&a_ptr): m_ptr(a_ptr) {
    a_ptr = NULL;
    }

    unique_ptr(unique_ptr &&p): m_ptr(p.release()) {
    cout << "Move" << endl;
    }

    T *release() {
    T *ptr = m_ptr;
    m_ptr = NULL;
    return ptr;
    }

    T *get() {
    return m_ptr;
    }

    T *operator->() {
    return m_ptr;
    }

    ~unique_ptr() {
    if(m_ptr != NULL) {
    delete m_ptr;
    }
    }

    private:
    unique_ptr(const unique_ptr &);
    void operator=(const unique_ptr &);
    void operator=(unique_ptr &&p);

    T *m_ptr;

    };

    struct Foo
    {
    Foo(int a): a(a) {
    cout << "Foo::ctor(" << a << ")" << endl;
    }

    ~Foo() {
    cout << "Foo::dtor()" << endl;
    }

    int a;
    private:
    Foo(const Foo &);
    Foo(Foo &&);

    };

    unique_ptr<Foo> source(int a = 0) {
    return move(unique_ptr<Foo>(new Foo(a)));

    }

    void sink(unique_ptr<Foo> a_foo) {
    cout << a_foo->a << endl;

    }

    int main() {
    unique_ptr<Foo> foo( source(1) );
    unique_ptr<Foo> bar = move(foo);
    assert(foo.get() == NULL); // ok

    unique_ptr<Foo> qux( source(2) );
    sink( move(qux) );
    assert(qux.get() == NULL); // ??
    }
    Micha³ 'Khorne' Rzechonek, Jan 28, 2009
    #1
    1. Advertising

  2. Michal 'Khorne' Rzechonek wrote:
    > I wanted o understand how rvalue references work, so I took GCC 4.3
    > with -std=c++0x flag and wrote code below.
    >
    > What I don't understand is why 2nd assertion fails and move ctor is
    > not called. Please enlighten me :)
    >
    > #include <iostream>
    > #include <cassert>
    >
    > using std::cout;
    > using std::endl;
    > using std::move;
    >
    > template<typename T>
    > class unique_ptr {
    > public:
    > explicit unique_ptr(T *&&a_ptr): m_ptr(a_ptr) {
    > a_ptr = NULL;
    > }
    >
    > unique_ptr(unique_ptr &&p): m_ptr(p.release()) {
    > cout << "Move" << endl;
    > }
    >
    > T *release() {
    > T *ptr = m_ptr;
    > m_ptr = NULL;
    > return ptr;
    > }
    >
    > T *get() {
    > return m_ptr;
    > }
    >
    > T *operator->() {
    > return m_ptr;
    > }
    >
    > ~unique_ptr() {
    > if(m_ptr != NULL) {
    > delete m_ptr;
    > }
    > }
    >
    > private:
    > unique_ptr(const unique_ptr &);
    > void operator=(const unique_ptr &);
    > void operator=(unique_ptr &&p);
    >
    > T *m_ptr;
    >
    > };
    >
    > struct Foo
    > {
    > Foo(int a): a(a) {
    > cout << "Foo::ctor(" << a << ")" << endl;
    > }
    >
    > ~Foo() {
    > cout << "Foo::dtor()" << endl;
    > }
    >
    > int a;
    > private:
    > Foo(const Foo &);
    > Foo(Foo &&);
    >
    > };
    >
    > unique_ptr<Foo> source(int a = 0) {
    > return move(unique_ptr<Foo>(new Foo(a)));
    >
    > }
    >
    > void sink(unique_ptr<Foo> a_foo) {
    > cout << a_foo->a << endl;
    >
    > }
    >
    > int main() {
    > unique_ptr<Foo> foo( source(1) );
    > unique_ptr<Foo> bar = move(foo);
    > assert(foo.get() == NULL); // ok
    >
    > unique_ptr<Foo> qux( source(2) );
    > sink( move(qux) );
    > assert(qux.get() == NULL); // ??
    > }


    I think the assertion should not fail. Could it be a compiler bug? It
    reminds me of GCC Bugzilla Bug 36744 - function modifying argument
    received by-value affects caller's variable when passed as rvalue
    http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36744


    > Side question: does source() function look all right?


    > unique_ptr<Foo> source(int a = 0) {
    > return move(unique_ptr<Foo>(new Foo(a)));


    Should work fine without the "move", as unique_ptr<Foo>(...) is an
    rvalue already:

    unique_ptr<Foo> source(int a = 0) {
    return unique_ptr<Foo>(new Foo(a));

    (Untested)


    HTH, Niels

    --
    Niels Dekker
    http://www.xs4all.nl/~nd/dekkerware
    Scientific programmer at LKEB, Leiden University Medical Center
    Niels Dekker - no return address, Jan 28, 2009
    #2
    1. Advertising

  3. Micha³ 'Khorne' Rzechonek

    SG Guest

    On 28 Jan., 10:35, Micha³ 'Khorne' Rzechonek <>
    wrote:
    > Hello,
    >
    > I wanted o understand how rvalue references work, so I took GCC 4.3
    > with -std=c++0x flag and wrote code below.


    [rearranged]

    > #include <iostream>
    > #include <cassert>


    Don't you need <utility> as well for std::move?

    > using std::cout;
    > using std::endl;
    > using std::move;
    >
    > template<typename T>
    > class unique_ptr {
    > public:
    >     explicit unique_ptr(T *&&a_ptr): m_ptr(a_ptr) {
    >         a_ptr = NULL;
    >     }


    That's unusual. But ok considering current rules. However, the
    semantics of "&&" may change, see:
    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/

    I would use

    explicit unique_ptr(T * a_ptr): m_ptr(a_ptr) {}

    instead.

    >     unique_ptr(unique_ptr &&p): m_ptr(p.release()) {
    >         cout << "Move" << endl;
    >     }
    >
    >     T *release() {
    >         T *ptr = m_ptr;
    >         m_ptr = NULL;
    >         return ptr;
    >     }
    >
    >     T *get() {
    >         return m_ptr;
    >     }
    >
    >     T *operator->() {
    >         return m_ptr;
    >     }


    The above two functions (get, operator->) should be const. Is there no
    overload for operator* ?

    >     ~unique_ptr() {
    >         if(m_ptr != NULL) {
    >             delete m_ptr;
    >         }
    >     }


    You don't need to check for null pointers here.

    >
    > private:
    >     unique_ptr(const unique_ptr &);
    >     void operator=(const unique_ptr &);
    >     void operator=(unique_ptr &&p);


    You don't need an extra && overload here for operator=.

    >     T *m_ptr;
    >
    > };
    >
    > struct Foo
    > {


    [snip]

    > };
    >
    > unique_ptr<Foo> source(int a = 0) {
    >     return move(unique_ptr<Foo>(new Foo(a)));
    > }
    >
    > void sink(unique_ptr<Foo> a_foo) {
    >     cout << a_foo->a << endl;
    >
    > }
    >
    > int main() {
    >     unique_ptr<Foo> foo( source(1) );
    >     unique_ptr<Foo> bar = move(foo);
    >     assert(foo.get() == NULL); // ok
    >
    >     unique_ptr<Foo> qux( source(2) );
    >     sink( move(qux) );
    >     assert(qux.get() == NULL); // ??
    >
    > }


    [rearranged]

    > What I don't understand is why 2nd assertion fails and move ctor is
    > not called. Please enlighten me :)


    It fails? That's odd. I can't test it myself right now, unfortunately.
    I guess it's either a compiler bug or we overlooked something.

    > Side question: does source() function look all right?


    Yes. You don't need the extra move(), though. You only need move() if
    you want to return a function's parameter or some other lvalue
    reference as rvalue. Local variables (not including call-by-value
    parameters) are automatically treated as rvalues in a return
    statement.

    Cheers!
    SG
    SG, Jan 28, 2009
    #3
  4. FYI: checked with GCC 4.4, assertion still fails.

    On 28 Sty, 11:16, SG <> wrote:
    > > public:
    > >     explicit unique_ptr(T *&&a_ptr): m_ptr(a_ptr) {
    > >         a_ptr = NULL;
    > >     }

    >
    > That's unusual. But ok considering current rules. However, the
    > semantics of "&&" may change


    Well, my intention was to move ownership from lvalues... I understand
    your point though.

    > > What I don't understand is why 2nd assertion fails and move ctor is
    > > not called. Please enlighten me :)

    >
    > It fails? That's odd. I can't test it myself right now, unfortunately.
    > I guess it's either a compiler bug or we overlooked something.


    Debugging the code shows that both objects have the same address, so
    it looks like ctor elision. I am not sure if move ctors can be eluded,
    they have side effects "by default", don't they?

    > You don't need the extra move(), though. You only need move() if
    > you want to return a function's parameter or some other lvalue
    > reference as rvalue. Local variables (not including call-by-value
    > parameters) are automatically treated as rvalues in a return
    > statement.


    Thanks for detailed explanation.

    Khorne
    Micha³ 'Khorne' Rzechonek, Jan 28, 2009
    #4
  5. Micha³ 'Khorne' Rzechonek

    SG Guest

    I was able to cut it down to a very small piece of code. The problem
    still exists. It seems to be a compiler bug.

    #include <iostream>
    #include <ostream>
    #include <cassert>
    #include <utility>

    using std::cout;
    using std::endl;
    using std::move;

    class movable {
    int resource; // 0=not acquired
    movable(const movable&); // no copy c'tor
    void operator=(const movable&); // no copy-assignment
    public:
    explicit movable(int i=0) : resource(i) {}
    int get() const {return resource;}
    int release() {int r=resource; resource=0; return r;}
    movable(movable && rval) : resource(rval.release())
    { cout << "movable::movable(movable &&)" << endl;}
    };

    void sink(movable m) {
    cout << "sink: &m --> " << &m << endl;
    cout << "sink: m.get() --> " << m.get() << endl;
    }

    int main() {
    movable m (42);
    cout << "main: &m --> " << &m << endl;
    sink(move(m));
    assert(m.get()==0);
    }

    The assertion fails with GCC 4.3.0. I can even remove the move-c'tor
    and it still compiles.

    sg@box:~/$ g++ --version
    g++ (Ubuntu 4.3.2-1ubuntu11) 4.3.2
    Copyright (C) 2008 Free Software Foundation, Inc.

    sg@box:~/$ ./test
    main: &m --> 0xbfd9eb00
    sink: &m --> 0xbfd9eb00
    sink: m.get() --> 42
    test: test.cpp:37: int main(): Assertion `m.get()==0' failed.
    Aborted

    Note: The move constructor is not invoked. The interesting part is
    that 'm' in main and 'm' in sink have the same address.

    Cheers!
    SG
    SG, Jan 28, 2009
    #5
  6. Micha³ 'Khorne' Rzechonek

    SG Guest

    On 28 Jan., 22:17, SG <> wrote:
    > I was able to cut it down to a very small piece of code. The problem
    > still exists. It seems to be a compiler bug.
    > [...]
    > The assertion fails with GCC 4.3.0. I can even remove the move-c'tor
    > and it still compiles.
    > [...]
    > Note: The move constructor is not invoked. The interesting part is
    > that 'm' in main and 'm' in sink have the same address.


    Just to clearify: The fact that the compiler accepts the code with
    copy- and move-c'tor being deleted (private) is probably a bug. G++
    does a move/copy elision here which is IMHO impressive because move()
    returns just a reference and a copy/move elision requires the
    inspection of the move() function to figure out that the rvalue
    reference refers to the local object 'm'. I honestly don't know
    whether this is allowed by the current draft or not. With move() you
    basically say "I don't care about what happens with 'm' here".

    Cheers!
    SG
    SG, Jan 28, 2009
    #6
  7. On Jan 28, 9:17 pm, SG <> wrote:
    > I was able to cut it down to a very small piece of code. The problem
    > still exists. It seems to be a compiler bug.
    >
    > #include <iostream>
    > #include <ostream>
    > #include <cassert>
    > #include <utility>
    >
    > using std::cout;
    > using std::endl;
    > using std::move;
    >
    > class movable {
    > int resource; // 0=not acquired
    > movable(const movable&); // no copy c'tor
    > void operator=(const movable&); // no copy-assignment
    > public:
    > explicit movable(int i=0) : resource(i) {}
    > int get() const {return resource;}
    > int release() {int r=resource; resource=0; return r;}
    > movable(movable && rval) : resource(rval.release())
    > { cout << "movable::movable(movable &&)" << endl;}
    > };
    >
    > void sink(movable m) {
    > cout << "sink: &m --> " << &m << endl;
    > cout << "sink: m.get() --> " << m.get() << endl;
    > }
    >
    > int main() {
    > movable m (42);
    > cout << "main: &m --> " << &m << endl;
    > sink(move(m));
    > assert(m.get()==0);
    > }
    >
    > The assertion fails with GCC 4.3.0. I can even remove the move-c'tor
    > and it still compiles.
    >
    > sg@box:~/$ g++ --version
    > g++ (Ubuntu 4.3.2-1ubuntu11) 4.3.2
    > Copyright (C) 2008 Free Software Foundation, Inc.
    >
    > sg@box:~/$ ./test
    > main: &m --> 0xbfd9eb00
    > sink: &m --> 0xbfd9eb00
    > sink: m.get() --> 42
    > test: test.cpp:37: int main(): Assertion `m.get()==0' failed.
    > Aborted
    >
    > Note: The move constructor is not invoked. The interesting part is
    > that 'm' in main and 'm' in sink have the same address.


    I've experienced this bug as well. A simple workaround is:

    void sink (movable&& r) {
    movable m (move (r));
    cout << "sink: &m --> " << &m << endl;
    cout << "sink: m.get () --> " << m.get () << endl;
    }

    Regards,
    Vidar Hasfjord
    Vidar Hasfjord, Jan 29, 2009
    #7
  8. Micha³ 'Khorne' Rzechonek

    SG Guest

    I searched the GCC bug database and it seems there is already a report
    on a similar case (#36744)

    http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36744

    Even in the case of a missing move constructor the copy must not be
    elided because the object in question is not a temporary.

    Cheers!
    SG
    SG, Jan 31, 2009
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Christopher
    Replies:
    5
    Views:
    1,426
    Thomas J. Gritzan
    Mar 18, 2009
  2. Jayden Shui

    Is this a bug of std::unique_ptr?

    Jayden Shui, Dec 16, 2011, in forum: C++
    Replies:
    4
    Views:
    816
  3. Jayden Shui
    Replies:
    2
    Views:
    354
    Jayden Shui
    Dec 16, 2011
  4. Brice Gagnage
    Replies:
    6
    Views:
    1,539
    Brice Gagnage
    Apr 5, 2012
  5. Martin Ba
    Replies:
    1
    Views:
    290
    Martin Ba
    Nov 23, 2012
Loading...

Share This Page