Move construction and assignment of class with constant member - c++

I have a class with a const member that needs both a move constructor and assignment.
I implemented it the following way:
struct C
{
const int i;
C(int i) : i{i} {}
C(C && other) noexcept: i{other.i} {}
C & operator=(C && other) noexcept
{
//Do not move from ourselves or all hell will break loose
if (this == &other)
return *this;
//Call our own destructor to clean-up before moving
this->~C();
//Use our own move constructor to do the actual work
new(this) C {std::move(other)};
return *this;
}
//Other stuff here (including the destructor)....
}
This compiles and works as expected.
The question is whether this is the normal way to implement such a move assignment or there is a less contrived way to do it?

Unfortunately this is undefined behavior. You cannot overwrite a const object like this and refer to it by the same name afterwards. This is covered by [basic.life]/8
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if: [...]
the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, [...]
The simple fix is to make i non const and private and just use a getter to stop any modification from happening.

After some research I came to the following.
There are related questions:
move assignment to object with const value
Placement new and assignment of class with const member
assignment of class with const member
The relevant information was found in:
https://timsong-cpp.github.io/cppwp/basic.life
https://timsong-cpp.github.io/cppwp/class.copy.assign
https://en.cppreference.com/w/cpp/language/lifetime#Storage_reuse
https://en.cppreference.com/w/cpp/utility/launder
What clarified this for me was the example in https://en.cppreference.com/w/cpp/utility/launder
struct X {const int i; };
X * p = new X{0};
X * np = new (p) X{1};
This will result in undefined behavior:
const int i = p->i
But the following is valid:
const int i = np->i;
Per my original question, a modified version of the move assignment would be needed:
struct C
{
const int i;
C() : i{} {}
C(C && other) noexcept: i{other.i} {}
C & operator=(C && other) noexcept
{
if (this == &other) return *this;
this->~C(); //Ok only if ~C is trivial
return *(new(this) C {std::move(other)});
}
}
C a;
C b;
b = std::move(a);
//Undefined behavior!
const int i = b.i;
This would work as expected but would result in undefined behavior for the following reasons.
When the destructor is invoked the objects' lifetime ends. Following that it is safe to call the move constructor. But at any point the compiler is free to assume that the content of b never changes. Thus, by using our move assignment we have a contradiction that results in undefined behavior.
On the other hand, although the return value from the placement new is the same as this, when the compiler performs access through that, returned, pointer/reference it must not assume anything about that object.
Given that C& C::operator=(C&&) returns the result of the placement new, the following should be valid (but not really useful).
const int i = (b = std::move(a)).i;
Thank to #NathanOliver, whos' answer was the correct one all along and, also, to him and #SanderDeDycker for playing brain ping-pong with me.

As of c++20, this can now be done without UB. This is because the basic.life now allows replacing objects that contain consts. Prior to c++20 this was disallowed.
Secondly, lack of UB can be proven using constexpr compile time evaluation. Compilers are required to detect UB in compile time evaluation. However, placement new is still not consteval so while legal in regular code w/o UB, it can't be checked in compile time functions. But c++20 provides 2 functions that will work, std::destroy_at and std::construct_at.
This code allows assignment using your example:
#include <memory>
struct C
{
const int i;
constexpr C(int i) : i{ i } {}
constexpr C(const C& other) noexcept = default;
constexpr C& operator=(const C& other) noexcept
{
//Do not move from ourselves or all hell will break loose
if (this == &other)
return *this;
//Call our own destructor to clean-up before moving
std::destroy_at(this);
std::construct_at(this, other);
return *this;
}
};
// Compilers are required to detect any UB evaluating this
consteval int foo()
{
C a(1), b(2); // create two difference objects
C c(b); // CTOR
a = c;
return a.i; // returns 2;
}
int main()
{
return foo();
}

Related

Is it possible now with the current C++ standard draft version to define a copy assignment operator for classes with const fields without UB

Here on SO we have had a lot of questions about assignments to const fields and Undefined Behavior (UB). For example This accepted answer which says that is not possible to define a copy assignment operator for classes with const fields because of UB.
But I checked the current draft version of the C++ standard (N4861). The part which said it would be UB [basic.life.8] was:
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:...
the type of the original object is not const-qualified, and, if a class type, does not contain any non-static
data member whose type is const-qualified or a reference type, and
Has now been replaced with:
o1 is not a complete const object, and
My interpretation is, that the following code now has no UB. Is it right? I ask this because in the sample, there is no cv qualified member - so it's still not clear for me.
#include <iostream>
struct C {
const int i;
void f() const {
std::cout << i << "\n";
}
C(int i) : i(i) {}
C& operator=( const C& );
};
C& C::operator=( const C& other) {
if ( this != &other ) {
this->~C(); // lifetime of *this ends
new (this) C(other); // new object of type C created
f(); // well-defined
}
return *this;
}
int main(){
C c1(1);
C c2(2);
c1 = c2; // well-defined
c1.f();
}
In fact my observation is that it works with all major compilers but that makes it not legal...

C++ referencing operator in declaration

I am a beginner in C++, and this must be a really basic question. I understand & stands for referencing operation. For example, a = &b assigns the address of b to a. However, what does & in a declaration such as the following mean?
className& operator=(const className&);
Do the following make sense and what is the difference between the last line and the following?
className* operator=(const className*);
From the answers below, it seems --- as I understand it --- that the following is valid too. Is it? If it is, how is it different from the version with "&"?
className operator=(const className);
After reading the following answers and some more outside, I realized part of my original confusion stems from mixing up reference as in general computer science and reference type as in C++. Thank you all who answered my question. All the answers clarify my understanding to different degrees, even though I can only pick one as the accepted answer.
The token & has three distinct meanings in C++, two of which are inherited from C, and one of which is not.
It's the bitwise AND operator (unless overloaded).
It's the address operator, which acts on an lvalue to yield a pointer to it. (Unless overloaded.) This is what is happening in a = &b.
It denotes a reference type. const className& as a parameter is a const reference to an object of type className, so when the function is called, the argument will be passed by reference, but it won't be allowed to be modified by the function. The function you gave also returns a reference.
Assignment Operator
Understanding is best gained by example:
class A {
int x;
public:
A(int value) : x(value) {}
A& operator=(const A& from) { // Edit: added missing '=' in 'operator='
x = from.x;
return *this;
}
};
A m1(7);
A m2(9);
m2 = m1; /// <--- calls the function above, replaces m2.x with 7
Here, we defined the assignment operator. This special method is designed to provide assignment capability to objects.
The reason that it returns a reference to *this is so you can chain assignments without excessive memory copies:
A m3(11);
m3 = m1 = m2; /// <--- now, m3.x and m1.x both set to m2.x
Expanded as follows:
m3 = ( something )
where
(something) is a reference to the object m1
by result of call to m1.operator=(m2) method
such that
the returned reference is then passed into m3.operator(...)
Chaining lets you do this:
(m1=m2).function(); // calls m1.function after calling assignment operator
Libraries such as boost leverage this pattern (for a different type of chaining example, see the program options library in boost) and it can be useful when developing a domain specific 'language'.
If instead full objects were returned from operator=, the chained assignment would at a minimum involve multiple extra copies of the objects, wasting CPU cycles and memory. In many cases things would not work properly because of the copy.
Essentially, using a reference avoids a copy.
Note
In reality, (simplified explanation) a reference is just a fancy syntax for a C pointer.
In the common case, you can then write code with A.x instead of A->x.
Caution
Returning a pure reference from a method is often dangerous; newcomers can be tempted to return a reference to an object constructed locally inside the method on the stack, which depending on the object can lead to obscure bugs.
Your pointer example
It depends on what you return from the body of the method, but regardless, the following would instead return a pointer to some instance of className:
className* operator=(const className*);
This will compile and it even seems to work (if you return this from the method), but this does violate the Rule of Least Surprise, as it is likely anyone else attempting to use your code would not expect the assignment operator to return a pointer.
If you think about base types:
int x=1; int y=2; int z; z=y=x;
will never ever do anything other than return integers - so having operator= return the assigned to object is consistent)
It also doesn't let you do this:
(m1 = m2).something
It also allows you to pass NULL which is something assignment operators don't typically want to care about.
Instead of writing
blah& operator(const blah& x) { a = x.a ; return *this; }
You would need to write:
blah* operator(const blah* x) {
if (x) { a = x->a ; return this; }
else { /*handle null pointer*/
}
It means the function takes a reference to a const className and returns a reference to a className
Say you have a class like below:
class className
{
public:
className& operator=(const className& rhs)
{
this->a_ = rhs.a_;
this->b_ = rhs.b_;
return *this;
}
private:
std::string a_;
std::string b_;
};
You can use the assignment operator of the class as below:
className a;
className b;
className c;
c = b = a;
The above line is executed as:
c.operator=(b.operator=(a));
Now take another class that has the operator= defined using the second form:
class className2
{
public:
className2* operator=(const className2* rhs)
{
this->a_ = rhs->a_;
this->b_ = rhs->b_;
return this;
}
private:
std::string a_;
std::string b_;
};
You can use the assignment operator of the class as below:
className2 obj1;
className2 obj2;
className2 obj3;
obj2 = &obj1;
obj3 = &obj2;
The above lines are executed as:
obj2.operator=(&obj1);
obj3.operator=(&obj2);
However, such coding is not intuitive. You don't assign a pointer to an object. It is more intuitive to say:
className obj1;
className* objPtr = NULL;
objPtr = &obj1;
a = &b
Here, & gives address of b.
className& operator=(const className&);
Here, operator = will take const reference of variable of type className.
You can say in C++, & is polymorphic.

Placement new to avoid copy constructor

I have a simple class that contains a pointer to one of it's own members:
struct X {
int val;
int* pVal;
X(int v) : val(v), pVal(&val) {}
}
X x(1);
I have some code like this:
void foo() {
doStuffWith(x);
x = X(2); // completely discard the old value of X, and start again
doStuffWith(x);
}
I'm worried that when x is reassigned, x.pVal will invalidly point to the member of the temporary X(2) if return value optimization does not occur.
I realize I could write a copy constructor to fix this. However, it seems wasteful to do the copy in the first place, rather than constructing the object in the right spot in memory to begin with.
Is it reasonable to use the placement new operator here? Or does this have unintented consequences for destructors?
void foo() {
doStuffWith(x);
new (&x) X(2); // completely discard the old value of X, and start again
doStuffWith(x);
}
The most obvious (and probably most effective) way to make this work is to provide copy assignment and copy construction operators to "do the right thing", something on this general order:
struct X {
int val;
int* pVal;
X(int v) : val(v), pVal(&val) {}
X(X const &other) : val(other.val), pVal(&val) {}
// pVal was already set by ctor, so just ignore it:
X &operator=(X const &other) { val = other.val; return *this; }
// and allow assignment directly from an int:
X &operator=(int n) { val = n; return *this; }
};
Then the rest of the code can just copy/assign X objects without jumping through hoops to prevent corruption.
No
It won't destruct the old value of x, but you're right the the default copy assignment operator won't do what you want either.
For me it seems quite acceptable solution if X's destructor will be called before placement new. It's syntactically allowed even the destructor is not actually specified for the class.
struct X {
int val;
int* pVal;
X(int v) : val(v), pVal(&val) {}
};
X x(1);
void foo() {
doStuffWith(x);
x.~X();
new (&x) X(2);
doStuffWith(x);
}
In this form it is a correct way to reuse memory for any object (but only if the object's ctor can't throw! otherwise UB may happen on program shutdown, i.e. double call of the destructor).
Indeed, the equality of pointers passed and returned from placement new in it non-array form is guaranteed by the standard:
18.6.1.3 Placement forms
...
void* operator new(std::size_t size, void* ptr) noexcept;
Returns: ptr.
Remarks: Intentionally performs no other action.
(and the result of the conversion to void* and then back to the same
pointer type is also guaranteed to be the same as the source pointer)
However, to avoid non-proper use of the class it would be more safe either define copy assignment and copy constructor or declare this class as noncopyable (with deleted ones)
And only the last (noncopyable) case may be regarded as a reason for using placement new.
Although I'm far from promoting placement new for general use, it express the intention to reuse object memory directly and doesn't rely on any optimization. Copy constructor and copy assignment are, of course, more safe, but don't express this intention excactly: no "copy" actually needed, the new object should be constructed in place of the old one.

std::vector of objects and const-correctness

Consider the following:
class A {
public:
const int c; // must not be modified!
A(int _c)
: c(_c)
{
// Nothing here
}
A(const A& copy)
: c(copy.c)
{
// Nothing here
}
};
int main(int argc, char *argv[])
{
A foo(1337);
vector<A> vec;
vec.push_back(foo); // <-- compile error!
return 0;
}
Obviously, the copy constructor is not enough. What am I missing?
EDIT:
Ofc. I cannot change this->c in operator=() method, so I don't see how operator=() would be used (although required by std::vector).
I'm not sure why nobody said it, but the correct answer is to drop the const, or store A*'s in the vector (using the appropriate smart pointer).
You can give your class terrible semantics by having "copy" invoke UB or doing nothing (and therefore not being a copy), but why all this trouble dancing around UB and bad code? What do you get by making that const? (Hint: Nothing.) Your problem is conceptual: If a class has a const member, the class is const. Objects that are const, fundamentally, cannot be assigned.
Just make it a non-const private, and expose its value immutably. To users, this is equivalent, const-wise. It allows the implicitly generated functions to work just fine.
An STL container element must be copy-constructible and assignable1(which your class A isn't). You need to overload operator =.
1
: §23.1 says The type of objects stored in these components must meet the requirements of CopyConstructible
types (20.1.3), and the additional requirements of Assignabletypes
EDIT :
Disclaimer: I am not sure whether the following piece of code is 100% safe. If it invokes UB or something please let me know.
A& operator=(const A& assign)
{
*const_cast<int*> (&c)= assign.c;
return *this;
}
EDIT 2
I think the above code snippet invokes Undefined Behaviour because trying to cast away the const-ness of a const qualified variable invokes UB.
You're missing an assignment operator (or copy assignment operator), one of the big three.
The stored type must meet the CopyConstructible and Assignable requirements, which means that operator= is needed too.
Probably the assignment operator. The compiler normally generates a default one for you, but that feature is disabled since your class has non-trivial copy semantics.
I think the STL implementation of vector functions you are using require an assignment operator (refer Prasoon's quote from the Standard). However as per the quote below, since the assignment operator in your code is implicitly defined (since it is not defined explicitly), your program is ill-formed due to the fact that your class also has a const non static data member.
C++03
$12.8/12 - "An implicitly-declared
copy assignment operator is implicitly
defined when an object of its class
type is assigned a value of its class
type or a value of a class type
derived from its class type. A program
is illformed if the class for which a
copy assignment operator is implicitly
defined has:
— a nonstatic data member of const type, or
— a nonstatic data
member of reference type, or
— a
nonstatic data member of class type
(or array thereof) with an
inaccessible copy assignment operator,
or
— a base class with an inaccessible
copy assignment operator.
Workaround without const_cast.
A& operator=(const A& right)
{
if (this == &right) return *this;
this->~A();
new (this) A(right);
return *this;
}
I recently ran into the same situation and I used a std::set instead, because its mechanism for adding an element (insert) does not require the = operator (uses the < operator), unlike vector's mechanism (push_back).
If performance is a problem you may try unordered_set or something else similar.
You also need to implement a copy constructor, which will look like this:
class A {
public:
const int c; // must not be modified!
A(int _c)
...
A(const A& copy)
...
A& operator=(const A& rhs)
{
int * p_writable_c = const_cast<int *>(&c);
*p_writable_c = rhs.c;
return *this;
}
};
The special const_cast template takes a pointer type and casts it back to a writeable form, for occasions such as this.
It should be noted that const_cast is not always safe to use, see here.
I just want to point out that as of C++11 and later, the original code in the question compiles just fine! No errors at all. However, vec.emplace_back() would be a better call, as it uses "placement new" internally and is therefore more efficient, copy-constructing the object right into the memory at the end of the vector rather than having an additional, intermediate copy.
cppreference states (emphasis added):
std::vector<T,Allocator>::emplace_back
Appends a new element to the end of the container. The element is constructed through std::allocator_traits::construct, which typically uses placement-new to construct the element in-place at the location provided by the container.
Here's a quick demo showing that both vec.push_back() and vec.emplace_back() work just fine now.
Run it here: https://onlinegdb.com/BkFkja6ED.
#include <cstdio>
#include <vector>
class A {
public:
const int c; // must not be modified!
A(int _c)
: c(_c)
{
// Nothing here
}
// Copy constructor
A(const A& copy)
: c(copy.c)
{
// Nothing here
}
};
int main(int argc, char *argv[])
{
A foo(1337);
A foo2(999);
std::vector<A> vec;
vec.push_back(foo); // works!
vec.emplace_back(foo2); // also works!
for (size_t i = 0; i < vec.size(); i++)
{
printf("vec[%lu].c = %i\n", i, vec[i].c);
}
return 0;
}
Output:
vec[0].c = 1337
vec[1].c = 999

C++ copy constructor causing code not to compile ( gcc )

I have the following code which doesn't compile. The compiler error is:
"error: no matching function to call B::B(B)",
candidates are B::B(B&) B::B(int)"
The code compiles under either of the following two conditions:
Uncommenting the function B(const B&)
change 'main' to the following
int main()
{
A a;
B b0;
B b1 = b0;
return 0;
}
If I do 1, the code compiles, but from the output it says it's calling the 'non const copy constructor'.
Can anyone tell me what's going on here?
using namespace std;
class B
{
public:
int k;
B()
{
cout<<"B()"<<endl;
}
B(int k)
{
cout<<"B(int)"<<endl;
this->k = k;
}
/*B(const B& rhs) {
cout<<"Copy constructor"<<endl;
k = rhs.k;
}*/
B(B& rhs)
{
cout<<"non const Copy constructor"<<endl;
k = rhs.k;
}
B operator=(B& rhs)
{
cout<<"assign operator"<<endl;
k = rhs.k;
return *this;
}
};
class A
{
public:
B get_a(void)
{
B* a = new B(10);
return *a;
}
};
int main()
{
A a;
B b0 = a.get_a(); // was a.just();
B b1 = b0 ;
return 0;
}
I've done some extra reading into this, and as I suspected all along, the reason why this occurs is due to return value optimization. As the Wikipedia article explains, RVO is the allowed mechanism by which compilers are allowed to eliminate temporary objects in the process of assigning them or copying them into permanent variables. Additionally, RVO is one of the few features (if not the only) which are allowed to violate the as-if rule, whereby compilers are only allowed to make optimizations only if they have the same observable behaviours as if the optimization were never made in the first place -- an exemption which is key in explaining the behaviour here (I admittedly only learned of that exemption as I was researching this question, which is why I was also confused initially).
In your case, GCC is smart enough to avoid one of the two copies. To boil your code down to a simpler example
B returnB()
{
B a;
B* b = &a;
return *b;
}
int main()
{
B c = returnB();
return 0;
}
If one follows the standard and does not perform RVO, two copies are made in the process of making c -- the copy of *b into returnB's return value, and the copy of the return value into c itself. In your case, GCC omits the first copy and instead makes only one copy, from *b directly into c. That also explains why B(B&) is called instead of B(const B&) -- since *b (a.k.a. a) is not a temporary value, the compiler doesn't need to use B(const B&) anymore and instead chooses the simpler B(B&) call instead when constructing c (a non-const overload is always automatically preferred over a const overload if the choice exists).
So why does the compiler still give an error if B(const B&) isn't there? That's because your code's syntax must be correct before optimizations (like RVO) can be made. In the above example, returnB() is returning a temporary (according to the C++ syntax rules), so the compiler must see a B(const B&) copy constructor. However, once your code is confirmed to be grammatically correct by the compiler, it then can make the optimization such that B(const B&) is never used anyway.
EDIT: Hat tip to Charles Bailey who found the following in the C++ standard
12.2 [class.temporary]: "Even when the creation of the temporary is avoided,
all the semantic restrictions must be
respected as if the temporary object
was created."
which just reinforces and confirms the need for a copy constructor taking a reference to const when temporaries are to be copied for construction (irrespective of whether or not the constructor is actually used)
Your get_a() function returns an object B, not a reference (but leaks the newly-created B object). Also, for assignments to work as you're doing, you need to make sure your assignment operator and copy constructors are both taking const B& arguments — then B b1 = b0 will work in this case. This works:
class B
{
public:
int k;
B() { cout<<"B()"<<endl; }
B(int k) {
cout<<"B(int)"<<endl;
this->k = k;
}
B(const B& rhs) {
cout<<"non const Copy constructor"<<endl;
k = rhs.k;
}
B operator=(const B& rhs) {
cout<<"assign operator"<<endl;
k = rhs.k;
return *this;
}
};
class A {
public:
B* get_a(void) {
B* a = new B(10);
return a;
}
B get_a2(void) {
B a(10);
return a;
}
};
int main() {
A a;
B b0 = *a.get_a(); // bad: result from get_a() never freed!
B b1 = a.get_a2(); // this works too
return 0;
}
Short explanation: functions that return by value create a temporary object which is treated as constant and therefore cannot be passed to functions accepting by reference, it can only be passed to functions accepting const reference. If you really allocate an object in get_a(), you should really be returning a pointer (so that you remember to delete it, hopefully) or in the worst case - a reference. If you really want to return a copy - create the object on the stack.
Long explanation: To understand why your code doesn't compile if there is only "non-const copy constructor"1, you need to be familiar with the terms lvalue and rvalue. They originally meant that rvalues can only stand on the right side of operator = (assignment) while lvalues can stand also on the left side. Example:
T a, b;
const T ac;
a = b; // a can appear on the left of =
b = a; // so can b => a and b are lvalues in this context
ac = a; // however, ac is const so assignment doesn't make sense<sup>2</sup>, ac is a rvalue
When the compiler is performing overload resolution (finding which overload of a function/method best match the provided arguments) it will allow lvalues to match parameters passed by value3, reference and const reference types. However, it will match rvalues only against value3 and const reference parameters. And that's because in some sense, since rvalues cannot be put on the left side of operator =, they have read-only semantic, and when it shouldn't be allowed to modify them. And when you accept a parameter through non-const reference, it's implied that you'll somehow change this parameter.
The last piece of the puzzle: temporary objects are rvalues. Function returning by value creates a temporary object with very limited lifespan. Because of its limited lifespan it's considered const, and is therefore a rvalue. And this rvalue doesn't match functions with parameters by non-const reference. Examples:
void f_cref(const A& a) { std::cout << "const ref" << std::endl; }
void f_ref(A& a) { std::cout << "non-const ref" << std::endl; }
A geta() { return A(); }
A a;
const A ac;
f_ref(a); // ok, a is a lvalue
f_ref(ac); // error, passing const to non-const - rvalue as lvalue - it's easy to spot here
f_cref(a); // ok, you can always pass non-const to const (lvalues to rvalues)
f_ref(geta()); // error, passing temporary and therefore const object as reference
f_cref(geta()); // ok, temporary as const reference
Now you have all the information to figure out why your code doesn't compile. Copy constructor are like regular functions.
I have oversimplified things a bit, so better, more complete and correct explanation can be found at this excellent Visual C++ Studio Team blog post about rvalue references, which also addresses the new C++ 0x feature "rvalue references"
1 - there's no such thing as non-const copy constructor. The copy constructor accepts const reference, period.
2 - you can probably put const object on the left of = if it has its operator = declared const. But that would be terrible, terrible, nonsensical thing to do.
3 - actually, you wouldn't be able to pass const A by value if A doesn't have a copy constructor - one that accepts const A& that is.
The line B b1 = b0; is the culprit. This line requires calling a copy constructor. You could fix the code by writing B b1(b0) instead, or by defining a copy constructor, which takes a const B&, but not a B&.