I wrote the following code where I try to copy the value of unique_ptr object into a structure.
#include <iostream>
#include <memory>
using namespace std;
struct S {
S(int X = 0, int Y = 0):x(X), y(Y){}
// S(const S&) {}
// S& operator=(const S&) { return *this; }
int x;
int y;
std::unique_ptr<S> ptr;
};
int main() {
S s;
s.ptr = std::unique_ptr<S>(new S(1, 4));
S p = *s.ptr; // Copy the pointer's value
return 0;
}
It pops up errors in Visual C++ 2012:
IntelliSense: no suitable user-defined conversion from "S" to "S"
exists
IntelliSense: no operator "=" matches these operands
operand types are: std::unique_ptr> = std::unique_ptr>
error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access
private member declared in class 'std::unique_ptr<_Ty>'
Unless I uncomment the lines where I attempted to define a copy constructor and =operator.
This gets rid of the compiler errors but not the IntelliSense errors. It compiles regardless of IntelliSense errors showing in error list.
So, why cannot it just use the default functions and compile with them? Am I doing the copy of value the right way? How should I define the copy constructor if it needs one?
The copy constructor is not implicitly generated because you have a user-defined constructor, which is why your attempt to copy an S fails.
But still, unique_ptr are not copyable, only movable, so you can use a move constructor for S :
S(S&& other) : x(other.x), y(other.y), ptr(std::move(other.ptr))
{
}
And call it :
S p = std::move(s); // Move s to p
Live demo
std::unique_ptr is neither Copy Constructible nor Copy Assignable.
An implicit copy assignment operator and constructor for S will be ill formed and hence the error message.
You can however use S p = std::move(s); as std::unique_ptr is Move Constructible and Move Assignable,
Not a complete answer, just informational:
I highly recommend adding visibility into your experiment:
std::ostream&
operator<<(std::ostream& os, const S& s)
{
os << '{' << s.x << ", " << s.y << ", ";
if (s.ptr != nullptr)
os << s.ptr.get() << ':' << *s.ptr;
else
os << "nullptr";
return os << '}';
}
Now you can say things like:
cout << "s = " << s << '\n';
at multiple places in your experiment, and really get a good visual on what is happening after each step. This should help you analyze and continue in your design.
So, why cannot it just use the default functions and compile with them?
As far as I understand, the idea behind unique_ptr container is that it solely handles the life of its content (a pointer to T), until being relieved from that duty (using swap or reset methods), or having effectively destroyed its content (when it is itself destroyed). The second important property of unique_ptr is that it must allow incomplete types for T (so as to support opaque pointers). That means that the contained value may not be CopyConstructible. Because of this, unique_ptr itself cannot be allowed to be CopyConstructible.
Am I doing the copy of value the right way?
How should I define the copy constructor if it needs one?
If T ends up being CopyConstructible, as you want to do it, you must handle the copy by hand, by accessing the pointer, as you are doing it in main. The copy constructor should probably do the same thing.
Related
While extending some pre-existing code, I ran into a situation involving a few nested classes and move construction that produced very unexpected behavior. I was eventually able to produce two possible fixes, but I'm not confident I fully understand the problem to begin with.
Here's a somewhat minimal example, in which a class Foo contains a field of type SubFoo and a unique pointer, and has different copy- and move-constructors to reflect ownership of the unique pointer. Note that there are three macros which are undefined --- corresponding to the original, working state of the code (i.e. none of the asserts fail).
#include <iostream>
#include <unordered_map>
#include <memory>
#include <vector>
#include <cassert>
//#define ADDMAP
//#define SUBFOO_MOVE
//#define FOO_MOVE_NONDEFAULT
class SubFoo {
public:
SubFoo() {}
SubFoo(const SubFoo& rhs) = default;
#ifdef SUBFOO_MOVE
SubFoo(SubFoo&& rhs) noexcept = default;
#endif
private:
#ifdef ADDMAP
std::unordered_map<uint32_t,uint32_t> _map;
#endif
};
class Foo {
public:
Foo(const std::string& name, uint32_t data)
: _name(name),
_data(std::make_unique<uint32_t>(std::move(data))),
_sub()
{
}
Foo(const Foo& rhs)
: _name(rhs._name),
_data(nullptr),
_sub(rhs._sub)
{
std::cout << "\tCopying object " << rhs._name << std::endl;
}
#ifdef FOO_MOVE_NONDEFAULT
Foo(Foo&& rhs) noexcept
: _name(std::move(rhs._name)),
_data(std::move(rhs._data)),
_sub(std::move(rhs._sub))
{
std::cout << "\tMoving object " << rhs._name << std::endl;
}
#else
Foo(Foo&& rhs) noexcept = default;
#endif
std::string _name;
std::unique_ptr<uint32_t> _data;
SubFoo _sub;
};
using namespace std;
int main(int,char**) {
std::vector<Foo> vec;
/* Add elements to vector so that it has to resize/reallocate */
cout << "ADDING PHASE" << endl;
for (uint i = 0; i < 10; ++i) {
std::cout << "Adding object " << i << std::endl;
vec.emplace_back(std::to_string(i),i);
}
cout << endl;
cout << "CHECKING DATA..." << endl;
for (uint i = 0; i < vec.size(); ++i) {
const Foo& f = vec[i];
assert(!(f._data.get() == nullptr || *f._data != i));
}
}
As mentioned above this is the working state of the code: as elements are added into the vector and it must be reallocated memory, the default move constructor is called rather than the copy constructor, as evidenced by the fact that "Copying object #" is never printed and the unique pointer fields remain valid.
However, after adding an unordered map field to SubFoo (which in my case wasn't completely empty, but only contained more basic types), the move constructor is no longer used when resizing/reallocating the vector. Here is a coliru link where you can run this code, which has the ADDMAP macro enabled and results in failed assertions because the copy constructor is called during vector resize and the unique pointers become invalid.
I eventually found two solutions:
Adding a default move constructor for SubFoo
Using a non-default move constructor for Foo that looks exactly like what I would have imagined the default move constructor did.
You can try these out in coliru by uncommenting either of the
SUBFOO_MOVE or FOO_MOVE_NONDEFAULT macros.
However, although I have some rough guesses (see postscripts), I mostly confused and don't really understand why the code was broken in the first place, nor why either of the fixes fixed it. Could someone provide a good explanation of what's going on here?
P.S. One thing I wonder, though I might be off track, is that if the presence of the unordered map in SubFoo somehow made move construction of Foo inviable, why doesn't the compiler warn that the = default move constructor is impossible?
P.P.S. Additionally, while in code shown here I've used "noexcept" move constructors wherever possible, I've had some compiler disagreement about whether this is possible. For example, clang warned me that for Foo(Foo&& rhs) noexcept = default, "error: exception specification of explicitly defaulted move constructor does not match the calculated one". Is this related to the above? Perhaps the move constructor used in vector resizing must be noexcept, and somehow mine wasn't really...
EDIT REGARDING NOEXCEPT
There's likely some compiler dependence here, but for the version of g++ used by coliru, the (default) move constructor for SubFoo does not need to have noexcept specified in order to fix the vector resizing issue (which is not the same thing as specifying noexcept(false), which does not work):
non-noexcept SubFoo move ctor works
while the custom move constructor for Foo must be noexcept to fix things:
non-noexcept Foo move ctor does not work
There is a standard defect (in my opinion) that unordered map's move ctor is not noexcept.
So the defaulted move ctor being noexcept(false) or deleted by your attempted default noexcept(true) seems plausible.
Vector resizing requires a noexecept(true) move ctor, because it cannot sanely and efficiently recover from the 372nd element's move throwing; it can neither roll back nor keep going. It would have to stop with a bunch of elements missing somehow.
I the following code I have explictly forbidden copying and moving Dummy object using delete specifier for copy and move constructors and for copy and move assignment operators:
#include <string>
#include <iostream>
struct Dummy
{
explicit Dummy(const std::string& value) : value_(value) {}
Dummy(const Dummy&) = delete;
Dummy& operator=(const Dummy&) = delete;
Dummy(Dummy&&) = delete;
Dummy& operator=(Dummy&&) = delete;
void print_this_address() const
{
std::cout << "this address: " << this << "\n";
}
std::string value_;
};
void foo(Dummy&& obj)
{
std::cout << obj.value_ << "\n";
obj.print_this_address();
}
int main()
{
Dummy obj("42");
obj.print_this_address();
foo(std::move(obj));
std::cout << obj.value_ << "\n";
return 0;
}
This code compiles and runs well and I'm getting the following output in my terminal:
this address: 0x7ffeeb6d2a20
42
this address: 0x7ffeeb6d2a20
42
Can someone explains for me what's happening in this code and why I didn't get compile error and have the same object in foo function as in main. function?
You never actually try to copy or move the object, which is why you don't get an error.
The parameter Dummy &&obj is an rvalue reference parameter, meaning it can bind to rvalues but not to lvalues. It's still a reference, just like const Dummy &obj would be (which would also work in your case).
What std::move does is not to move anything, it casts its parameter to an rvalue reference. This makes it possible to pass it to functions expecting an rvalue (for example, a move constructor). For more details have a look at the answer linked by StoryTeller.
It's not because you called std::move that your object has moved. It was just "prepared" to be moved.
As such, you prepared obj but never tried to move it somewhere else, hence the no compiler error, and as you never actually moved it (you just passed an rvalue reference to your object into foo), you get the same result, because obj never changed.
The book I'm reading says that when your class contains a member that's a reference or a const, using the compiler-generated copy constructor or assignment operators won't work. For instance,
#include <iostream>
#include <string>
using namespace std;
class TextBlock
{
public:
TextBlock (string str) : s(str) {
cout << "Constructor is being called" << endl;
}
string& s;
};
int main () {
TextBlock p("foo");
TextBlock q(p);
q = p;
cout << "Q's s is " << q.s << endl;
return(0);
}
According to my book, both the lines TextBlock q(p); and q = p; should return compiler errors. But using the g++ compiler for Linux, I'm only getting an error for the line q = p; When I comment that out, this works fine and the code compiles. The correct s is output for Q, so it's apparently being copied by the compiler-generated copy constructor. I get the same results when I change the line string& s; to const string s.
Have there been some changes to C++ that now allow the copy constructor to be generated automatically for reference and const objects, but not the assignment operator? Or maybe I'm just not understanding the book correctly? Any thoughts?
The book is wrong. A const member or a reference member will
inhibit generation of the default copy assignment operator, but
doesn't prevent the compiler from generating a copy constructor.
Don't try to learn a special rule here.
The compiler-generated default versions of special member functions follow a simple pattern:
The type-appropriate special member function is called for every subobject (base classes and members).
From that, you can work out every case.
int i;
int &ri1 = i;
int &ri2 = ri1;
is allowed, so copying an object containing an int& is allowed.
There is no assignment operator for references (ri2 = ri1; does not rebind the reference), so assignment is not allowed.
References can't be default constructed:
int& ri; // error
so a type containing int& can't be default-constructed.
An important consideration is that the access checks are done for compiler-defaulted code just as if you had written it yourself. So interesting things can happen if a base class has, for example, a private copy constructor... and you don't have to learn special rules for any of them.
I read the following article about rvalue references http://thbecker.net/articles/rvalue_references/section_01.html
But there are some things I did not understand.
This is the code i used:
#include <iostream>
template <typename T>
class PointerHolder
{
public:
// 1
explicit PointerHolder(T* t) : ptr(t)
{
std::cout << "default constructor" << std::endl;
}
// 2
PointerHolder(const PointerHolder& lhs) : ptr(new T(*(lhs.ptr)))
{
std::cout << "copy constructor (lvalue reference)" << std::endl;
}
// 3
PointerHolder(PointerHolder&& rhs) : ptr(rhs.ptr)
{
rhs.ptr = nullptr;
std::cout << "copy constructor (rvalue reference)" << std::endl;
}
// 4
PointerHolder& operator=(const PointerHolder& lhs)
{
std::cout << "copy operator (lvalue reference)" << std::endl;
delete ptr;
ptr = new T(*(lhs.ptr));
return *this;
}
// 5
PointerHolder& operator=(PointerHolder&& rhs)
{
std::cout << "copy operator (rvalue reference)" << std::endl;
std::swap(ptr, rhs.ptr);
return *this;
}
~PointerHolder()
{
delete ptr;
}
private:
T* ptr;
};
PointerHolder<int> getIntPtrHolder(int i)
{
auto returnValue = PointerHolder<int>(new int(i));
return returnValue;
}
If I comment constructors 2 and 3, the compiler says :
error: use of deleted function ‘constexpr PointerHolder<int>::PointerHolder(const PointerHolder<int>&)’
auto returnValue = PointerHolder<int>(new int(i));
^
../src/rvalue-references/move.cpp:4:7: note: ‘constexpr PointerHolder<int>::PointerHolder(const PointerHolder<int>&)’ is implicitly declared as deleted because ‘PointerHolder<int>’ declares a move constructor or move assignment operator
But If I uncomment any one of the two, it compiles and the execution yields the following :
default constructor
So these are my questions :
When the constructors 2 and 3 where commented, it tried to call the constructor 2. Why ? I would expect it to call the constructor 1, which is what it did when i uncommented them !
Regarding the error : I declared a "move" copy operator, which means that my object can be "move" copied from a rvalue reference. But why does it implicitly delete my normal copy constructor ? And if so why does it allow me to "undelete" it by defining it explicitly and use it ?
When the constructors 2 and 3 where commented, it tried to call the constructor 2. Why ?
Because your declaration initialises returnValue from a temporary object - that temporary needs to be movable or copyable, using a move or copy constructor. When you comment these out, and inhibit their implicit generation by declaring a move-assignment operator, they are not available, so the initialisation is not allowed.
The actual move or copy should be elided, which is why you just see "default constructor" when you uncomment them. But even when elided, the appropriate constructor must be available.
why does it implicitly delete my normal copy constructor ?
Usually, if your class has funky move semantics, then the default copy semantics will be wrong. For example, it might copy a pointer to an object which is only supposed to be pointed to by a single instance of your class; which might in turn lead to double deletion or other errors. (In fact, your move constructor does exactly this, since you forgot to nullify the argument's pointer).
It's safer to delete the copy functions, and leave you to implement them correctly if you need them, than to generate functions which will almost certainly cause errors.
And if so why does it allow me to "undelete" it by defining it explicitly and use it ?
Because you often want to implement copy semantics as well as move semantics.
Note that it's more conventional to call 3 a "move constructor" and 5 a "move-assignment operator", since they move rather than copy their argument.
Because you're deleting the copy constructor and the line
auto returnValue = PointerHolder<int>(new int(i));
isn't a real assignment, it invokes a copy constructor to build the object. One of the two copy constructors (either by reference or by rvalue) needs to be available in order to succeed in initializing the object from that temporary. If you comment those both out, no luck in doing that.
What happens if everything is available? Why aren't those called?
This is a mechanism called "copy elision", basically by the time everything would be properly available to "initialize" returnValue with a copy-constructor, the compiler's being a smartboy and realizing:
"oh, I could just initialize returnValue like this"
PointerHolder<int> returnValue(new int(i));
and this is exactly what happens when everything is available.
As for why the move constructor seems to overcome the implicit copy-constructor, I can't find a better explanation than this: https://stackoverflow.com/a/11255258/1938163
You need a copy or move constructor to construct your return value.
If you get rid of all copy/move constructors and all assignment operators (use default generated constructors/operators) the code will compile, but fail miserably due to multiple deletions of the member ptr.
If you keep the copy/move constructors and assignment operators you might not see any invocation of a constructor, due to copy elision (return value optimization).
If you disable copy elision (g++: -fno-elide-constructors) the code will fail again due to multiple deletions of the member ptr.
If you correct the move-constructor:
// 3
PointerHolder(PointerHolder&& rhs) : ptr(0)
{
std::swap(ptr, rhs.ptr);
std::cout << "copy constructor (rvalue reference)" << std::endl;
}
And compile with disabled copy elision the result might be:
default constructor
copy constructor (rvalue reference)
copy constructor (rvalue reference)
copy constructor (rvalue reference)
How does one control which constructor/assignment operator is being used to insert elements into the std::vector class? I tried to do it by deleteing the constructor/assignment I wanted to avoid using as follows
#include<iostream>
#include<vector>
using namespace std;
class copyer{
double d;
public:
//ban moving
copyer(copyer&& c) = delete;
copyer& operator=(copyer&& c) = delete;
//copy construction
copyer(const copyer& c){
cout << "Copy constructor!" << endl;
d = c.d;
}
copyer& copy(const copyer& c){
cout << "Copy constructor!" << endl;
d = c.d;
return *this;
}
//Constructor
copyer(double s) : d(s) { }
double fn(){return d;}
};
class mover{
double d;
public:
//ban copying
mover(const mover& c) = delete;
mover& operator=(const mover& c) = delete;
//move construction
mover(mover&& c){
cout << "Move constructor!" << endl;
d = c.d;
}
mover& copy(mover&& c){
cout << "Move constructor!" << endl;
d = c.d;
return *this;
}
//Constructor
mover(double s) : d(s) { }
double fn(){return d;}
};
template<class P> class ConstrTests{
double d;
size_t N;
std::vector<P> object;
public:
ConstrTests(double s, size_t n) : d(s) , N(n) {
object.reserve(N);
for(int i = 0; i<N; i++){
object.push_back(P((double) i*d));
}
}
void test(){
int i = 0;
while(i<N){
cout << "Testing " <<i+1 << "th object: " << object.at(i).fn();
i++;
}
}
};
When I compile and run
size_t N = 10;
double d = 4.0;
ConstrTests<mover> Test1 = ConstrTests<mover>(d,N);
Test1.test();
I have no problems, but if instead I try
size_t N = 10;
double d = 4.0;
ConstrTests<copyer> Test1 = ConstrTests<copyer>(d,N);
Test1.test();
I get an error at compilation stating I'm trying to use a deleted move constructor.
If you remove these lines from copyer
//ban moving
copyer(copyer&& c) = delete;
copyer& operator=(copyer&& c) = delete;
then the code compiles fine and works as expected, that is, std::vector<copyer> uses the copy-constructor and std::vector<mover> uses the move-constructor.
You had declared copyer's move constructor and defined it as deleted. It means that copyer::copyer(copyer&& c) participates in overload resolution but if selected the code is ill formed. The call object.push_back(P((double) i*d)); triggers this call.
Why removing the lines above fixed the issue?
In old C++98 if we don't declare a copy-constructor the compiler declares and implement one for us. In C++11 this rules has changed a little bit. The compiler will not implicitly define a copy-constructor if a move constructor is user declared. (There's more on this subject but for this discussion this is enough.) Analogously, if we don't declare a move-constructor the compiler will implicitly define one for us unless we declare (for instance) a copy-constructor.
Now, after having removed the lines above, copyer will have a user declared copy-constructor buy not a move-constructor. Then, neither you nor the compiler would have declared a move-constructor. In that case, object.push_back(P((double) i*d)); will trigger a call to the copy-constructor. Notice that this is exaclty what would have happened if we compiled with a C++98 compliant compiler. In this case backward compatibility holds and old code doesn't break.
The version which is called, is determined by overload resolution. If you call push_back with a prvalue or a xvalue it'll use the move constructor, assuming your move constructor is noexcept, if it is not, it'll still use the copy constructor, if the copy constructor is deleted, you're forcing the vector to use your throwing move constructor, in which case the vector can no longer provide the strong exception guarantee. (Ie. If an exception happens from your move constructor, you've broken the content of the vector).
The general idea is, that whenever you do a push_back, reallocation may be needed, if this is the case, all elements will have to be transfered to the new memory block, using either a move or a copy constructor. Using the move constructor is generally nice, if we're sure it won't throw anything, because in the case it does, we've may already have moved some objects from the original vector, which is now broken. And moving them back is not an alternative as that may throw as well. This is why you loose the strong exception guarantee.
So in general, please do declare your move constructor noexcept, if you'd like it to be called over the copy constructor. (In the above example, if the copy constructor throws, you can just deallocate the new memory, and rethrow the exception, and the vector is still as before the push_back call).
So in general, you control which is called, based upon which constructors are available, and upon the type of the argument (lvalue vs prvalue/xvalue). Assuming your constructors are in place, you'll likely control it, by calling push_back, using std::move on it's argument, this effectively converts the argument to an xvalue, if possible.
Note; whatever I said about push_back, generally applies to insert as well.
Note2: the guarantee I'm talking about is, that whenever push_back is called, we're guaranteed that if an exception happens, the call will not have any effect, this is known as the strong exception guarantee, and it cannot be provided if all you got is a throwing move constructor.