I am trying to understand the way move constructors and assignment ops work in C++11 but I'm having problems with delegating to parent classes.
The code:
class T0
{
public:
T0() { puts("ctor 0"); }
~T0() { puts("dtor 0"); }
T0(T0 const&) { puts("copy 0"); }
T0(T0&&) { puts("move 0"); }
T0& operator=(T0 const&) { puts("assign 0"); return *this; }
T0& operator=(T0&&) { puts("move assign 0"); return *this; }
};
class T : public T0
{
public:
T(): T0() { puts("ctor"); }
~T() { puts("dtor"); }
T(T const& o): T0(o) { puts("copy"); }
T(T&& o): T0(o) { puts("move"); }
T& operator=(T const& o) { puts("assign"); return static_cast<T&>(T0::operator=(o)); }
T& operator=(T&& o) { puts("move assign"); return static_cast<T&>(T0::operator=(o)); }
};
int main()
{
T t = std::move(T());
return 0;
}
However, when I compile and run under VS2012, the output indicates that the lvalue versions of the T0 members are called:
ctor 0
ctor
copy 0 <--
move <--
dtor
dtor 0
dtor
dtor 0
A similar situation (with a slightly different test case) happens with move assignments -- the move assignment operator of T calls the "normal" assignment operator of T0.
What am I doing wrong?
One of the more confusing things about functions taking rvalue references as parameters is that internally they treat their parameters as lvalues. This is to prevent you from moving the parameter before you mean to, but it takes some getting used to. In order to actually move the parameter, you have to call std::move (or std::forward) on it. So you need to define your move constructor as:
T(T&& o): T0(std::move(o)) { puts("move"); }
and your move assignment operator as:
T& operator=(T&& o) { puts("move assign"); return static_cast<T&>(T0::operator=(std::move(o))); }
You're only ever calling your base class's stuff with lvalues:
void foo(int&){} // A
void foo(int&&){} // B
void example(int&& x)
{
// while the caller had to use an rvalue expression to pass a value for x,
// since x now has a name in here it's an lvalue:
foo(x); // calls variant A
}
example(std::move(myinteger)); // rvalue for us, lvalue for example
That is, you need:
T(T&& o):
T0(std::move(o)) // rvalue derived converts to rvalue base
{
puts("move");
}
And:
T& operator=(T&& o)
{
puts("move assign");
T0::operator=(std::move(o)));
return *this;
}
Related
What is the difference between returning *this or the given argument in implementation of operator= in C++? Is using one of them better or more useful? if yes, why?
class Object {
public:
Object operator=(Object Obj) {
return *this;
}
}
vs.
class Object {
public:
Object operator=(Object Obj) {
return Obj;
}
}
X& operator=( X const& ) { return *this; } matches the semantics of = on an int. The other suggestions you gave do not. When in doubt match the semantics of int.
I'm trying to write a unique_ptr implementation. I'm struggling with writing a move constructor. Here are my problems:
When I mark the move constructor as default, my resource is deleted twice, when I move assign a pointer (auto foo2 = std::move(foo); below) - why?
When I'm trying to assign the underlying pointer in the move constructor like this *rhs = nullptr (see implementation below), the compiler says *rhs is an rvalue and that I cannot assign anything to it.
Finally, rhs.m_ptr = nullptr works. Why does it work, when *rhs = nullptr doesn't?
My code:
#include <iostream>
namespace my
{
template <class T>
class unique_ptr
{
public:
unique_ptr()
{
m_ptr = new T;
}
unique_ptr(const unique_ptr&) = delete;
// move constructor
unique_ptr(unique_ptr&& rhs) // = default deletes m_ptr twice
{
m_ptr = *rhs;
rhs.m_ptr = nullptr; // *rhs = nullptr doesn't work (*rhs is an rvalue)
}
~unique_ptr()
{
delete m_ptr;
}
T* operator->()
{
return m_ptr;
}
T* operator*()
{
return m_ptr;
}
unique_ptr& operator=(const unique_ptr&) = delete;
// no move assignment yet
private:
T* m_ptr;
};
} // namespace my
struct Foo
{
Foo()
{
std::cout << "Foo" << std::endl;
}
~Foo()
{
std::cout << "~Foo" << std::endl;
}
void printHello()
{
std::cout << "Hello" << std::endl;
}
};
int main()
{
my::unique_ptr<Foo> foo;
foo->printHello();
auto foo2 = std::move(foo);
return 0;
}
On a side note, apparently I can pass a unique_ptr without any template parameter to methods inside the unique_ptr class template. Does compiler just assume it's T?
Please discard any other implementation faults that don't relate to the described problems. It's work in progress.
1) The default move constructor doesn't know about the semantics of your class. So it moves the pointer rhs, but it will not reset the other pointer, which will get deleted as well in the other destructor.
2) *rhs calls operator* and returns a temporary/rvalue T*, a copy of the internal pointer, and is not consistent with the usual operator* which should return a T& or a const T&.
3) see 2. you are returning a temporary object.
So finally, what you should have:
unique_ptr(unique_ptr&& rhs) // = default deletes m_ptr twice
: m_ptr(rhs.m_ptr)
{
rhs.m_ptr = nullptr; // *rhs = nullptr doesn't work (*rhs is an rvalue)
}
T& operator*() {return *m_ptr;}
const T& operator*() const {return *m_ptr;}
And so on.
You're trying too hard. You don't have to go through the external interface. Just assign values:
m_ptr = rhs.m_ptr;
rhs.m_ptr = nullptr;
In addition, operator*() should return a T&, not a T*.
Let us say I have a move-only type. We stop the default-provided constructors from existing, but Rvalue references introduce a new "flavor" we can use for the moving-versions of the signatures:
class CantCopyMe
{
private:
CantCopyMe (CantCopyMe const & other) = delete;
CantCopyMe & operator= (CantCopyMe const & other) = delete;
public:
CantCopyMe (CantCopyMe && other) {
/* ... */
}
CantCopyMe & operator= (CantCopyMe && other) {
/* ... */
}
};
I recently thought you were always supposed to pass movable types by Rvalue reference. Now it's looking like only very special cases need to do that...like these two. Things had seemed to work most of the time if you put them everywhere, but I just found one case of the compiler not running the part of the code transferring ownership.
(It was a situation like passing a unique pointer held in a variable with std::move to something taking a unique_ptr<foo> && parameter...but noticing the variable at the callsite hadn't been nulled. Changing the parameter to unique_ptr<foo> fixed it and it was properly nulled, thus preventing a double-delete. :-/ I haven't isolated why this one was bad when it seemed to work elsewhere, but a smoking gun is it worked the first time but not the subsequent calls.)
I'm sure there's a good reason for that, and many of you can saliently sum it up. In the meantime I've started going around like a good cargo-cult programmer removing the &&s.
But what if you're writing a templated class, where it looked like this?
template <class FooType>
class CantCopyMe
{
private:
CantCopyMe (CantCopyMe const & other) = delete;
CantCopyMe & operator= (CantCopyMe const & other) = delete;
public:
template<class OtherFooType>
CantCopyMe (CantCopyMe<OtherFooType> && other) {
/* ... */
}
template<class OtherFooType>
CantCopyMe & operator= (CantCopyMe<OtherFooType> && other) {
/* ... */
}
};
Is that bad practice for some reason, and you should break out separately when OtherFooType and FooType aren't the same... then it just passes by value?
template <class FooType>
class CantCopyMe
{
private:
CantCopyMe (CantCopyMe const & other) = delete;
CantCopyMe & operator= (CantCopyMe const & other) = delete;
public:
CantCopyMe (CantCopyMe && other) {
/* ... */
}
CantCopyMe & operator= (CantCopyMe && other) {
/* ... */
}
template<class OtherFooType>
CantCopyMe (CantCopyMe<OtherFooType> other) {
/* ... */
}
template<class OtherFooType>
CantCopyMe & operator= (CantCopyMe<OtherFooType> other) {
/* ... */
}
};
I think there's a simple answer for a possibly unexpected reason:
A copy-/move constructor or assignment-operator is never a template (specialization). E.g. [class.copy]/2
A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments.
Also, footnote 122 says:
Because a template assignment operator or an assignment operator taking an rvalue reference parameter is never a copy assignment operator, the presence of such an assignment operator does not suppress the implicit declaration of a copy assignment operator. Such assignment operators participate in overload resolution with other assignment operators, including copy assignment operators, and, if selected, will be used to assign an object.
Example:
#include <iostream>
#include <utility>
template<class T>
struct X
{
X() {}
template<class U>
X(X<U>&&)
{
std::cout << "template \"move\" ctor\n";
}
template<class U>
X& operator= (X<U>&&)
{
std::cout << "template \"move\" assignment-op\n";
return *this;
}
};
int main()
{
X<int> x; // no output
X<int> y(x); // no output
y = std::move(x); // no output
X<double> z( std::move(x) ); // output
y = std::move(z); // output
}
In this example, the implicitly-declared move constructor and move assignment-operator are used.
Therefore, if you don't declare a non-template move ctor and move assignment-operator, they might be declared implicitly. They're not declared implicitly e.g. for the move assignment-op, if you have a user-declared dtor; for details see [class.copy]/11 and [class.copy]/20.
Example: Adding a dtor to the example above:
#include <iostream>
#include <utility>
template<class T>
struct X
{
X() {}
~X() {}
template<class U>
X(X<U>&&)
{
std::cout << "template \"move\" ctor\n";
}
template<class U>
X& operator= (X<U>&&)
{
std::cout << "template \"move\" assignment-op\n";
return *this;
}
};
int main()
{
X<int> x; // no output
X<int> y(x); // no output
y = std::move(x); // output
X<double> z( std::move(x) ); // output
y = std::move(z); // output
}
Here, the first move-assignment y = std::move(x); calls a specialization of the assignment-operator template, because there's no implicitly declared move assignment-operator.
I am trying to understand the way move constructors and assignment ops work in C++11 but I'm having problems with delegating to parent classes.
The code:
class T0
{
public:
T0() { puts("ctor 0"); }
~T0() { puts("dtor 0"); }
T0(T0 const&) { puts("copy 0"); }
T0(T0&&) { puts("move 0"); }
T0& operator=(T0 const&) { puts("assign 0"); return *this; }
T0& operator=(T0&&) { puts("move assign 0"); return *this; }
};
class T : public T0
{
public:
T(): T0() { puts("ctor"); }
~T() { puts("dtor"); }
T(T const& o): T0(o) { puts("copy"); }
T(T&& o): T0(o) { puts("move"); }
T& operator=(T const& o) { puts("assign"); return static_cast<T&>(T0::operator=(o)); }
T& operator=(T&& o) { puts("move assign"); return static_cast<T&>(T0::operator=(o)); }
};
int main()
{
T t = std::move(T());
return 0;
}
However, when I compile and run under VS2012, the output indicates that the lvalue versions of the T0 members are called:
ctor 0
ctor
copy 0 <--
move <--
dtor
dtor 0
dtor
dtor 0
A similar situation (with a slightly different test case) happens with move assignments -- the move assignment operator of T calls the "normal" assignment operator of T0.
What am I doing wrong?
One of the more confusing things about functions taking rvalue references as parameters is that internally they treat their parameters as lvalues. This is to prevent you from moving the parameter before you mean to, but it takes some getting used to. In order to actually move the parameter, you have to call std::move (or std::forward) on it. So you need to define your move constructor as:
T(T&& o): T0(std::move(o)) { puts("move"); }
and your move assignment operator as:
T& operator=(T&& o) { puts("move assign"); return static_cast<T&>(T0::operator=(std::move(o))); }
You're only ever calling your base class's stuff with lvalues:
void foo(int&){} // A
void foo(int&&){} // B
void example(int&& x)
{
// while the caller had to use an rvalue expression to pass a value for x,
// since x now has a name in here it's an lvalue:
foo(x); // calls variant A
}
example(std::move(myinteger)); // rvalue for us, lvalue for example
That is, you need:
T(T&& o):
T0(std::move(o)) // rvalue derived converts to rvalue base
{
puts("move");
}
And:
T& operator=(T&& o)
{
puts("move assign");
T0::operator=(std::move(o)));
return *this;
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Preventing non-const lvalues from resolving to rvalue reference instead of const lvalue reference
Conflict between copy constructor and forwarding constructor
I have these classes that I need for storing std::unique_ptr (adapted boost::any):
class any
{
public:
any()
: content(0)
{
}
any(any const&) = delete;
any(any && other)
: content(other.content)
{
content = 0;
}
template<typename ValueType>
any(ValueType const& value)
: content(new holder<ValueType>(value))
{
}
template<typename ValueType>
any(ValueType && value,
typename std::enable_if<!std::is_lvalue_reference<ValueType>::value,
void>::type* = 0)
: content(new holder<ValueType>(std::move(value)))
{
}
~any()
{
delete content;
}
public: // modifiers
any & swap(any & rhs)
{
std::swap(content, rhs.content);
return *this;
}
any & operator=(const any &) = delete;
any & operator=(any && rhs)
{
return swap(rhs);
}
template<typename ValueType>
any & operator=(ValueType const& rhs)
{
any(rhs).swap(*this);
return *this;
}
template<typename ValueType>
typename std::enable_if<!std::is_lvalue_reference<ValueType>::value,
any&>::type operator=(ValueType && rhs)
{
any(std::move(rhs)).swap(*this);
return *this;
}
public: // queries
bool empty() const
{
return !content;
}
const std::type_info & type() const
{
return content ? content->type() : typeid(void);
}
private: // types
class placeholder
{
public: // structors
virtual ~placeholder()
{
}
public: // queries
virtual const std::type_info & type() const = 0;
};
template<typename ValueType>
class holder : public placeholder
{
public: // structors
template <class T>
holder(T && value)
: held(std::forward<T>(value))
{
}
holder & operator=(const holder &) = delete;
public: // queries
virtual const std::type_info & type() const
{
return typeid(ValueType);
}
public:
ValueType held;
};
private: // representation
template<typename ValueType>
friend ValueType * any_cast(any *);
template<typename ValueType>
friend ValueType * unsafe_any_cast(any *);
placeholder * content;
};
and this test case:
any a;
any b(a);
b = a;
and this one:
std::map<int, int> map({{1,1},{2,2}});
any b(map);
std::cout << map.size() << std::endl; // displays 0
To my horror, under gdb, I've noticed that the move constructor and the move assignment operator are called when constructing and assigning b (even from map), even though I did not tag a with std::move and it is not a temporary. Can someone explain why?
My first answer was wrong. After reading through your very unreadable code again I see that you have explicitly provided a move and default constructor, but no copy constructor. If a class has any user defined constructor (of which you have two), the compiler will not generate any other constructors for that class. Hence, your class does not have a copy constructor.
Edit: So, back to my original answer (prompted by your comment). §12.8/7 [class.copy] says:
A member function template is never instantiated to perform the copy
of a class object to an object of its class type. [Example:
struct S {
template<typename T> S(T);
template<typename T> S(T&&);
S();
};
S f();
const S g;
void h() {
S a( f() ); // does not instantiate member template;
// uses the implicitly generated move
constructor S a(g); // does not instantiate the member template;
// uses the implicitly generated copy constructor
}
—end example ]
Since your copy contructor is a member-template, but your move constructor is not, the later is chosen here (your case is different from the example in that respect).