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

  • Thread starter Micha³ 'Khorne' Rzechonek
  • Start date
M

Micha³ 'Khorne' Rzechonek

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); // ??
}
 
N

Niels Dekker - no return address

Michal said:
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
 
S

SG

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 said:
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
 
M

Micha³ 'Khorne' Rzechonek

FYI: checked with GCC 4.4, assertion still fails.

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.
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
 
S

SG

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
 
S

SG

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
 
V

Vidar Hasfjord

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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,710
Latest member
bernietqt

Latest Threads

Top