Overloading of = operator for a class - c++

I was recently refreshing my C++ knowledge in operator overloading. As recommended I return a reference to *this for the operator overload of '='. But then I found one problem :-
#include <iostream>
using namespace std;
class MyClass
{
int num = 4;
public:
MyClass() = default;
MyClass(int x) : num(x) {}
void getnum(int x)
{
num = x;
}
void shownum()
{
cout << num << '\n';
}
MyClass& operator = (const MyClass& obj) // works even without const
{
this->num = obj.num;
return *this;
}
~MyClass() = default;
};
int main()
{
MyClass x, y(5), z(7);
z = MyClass(8) = y; // temporary object return reference
x.shownum();
y.shownum();
z.shownum();
}
This code doesnt result into a UB even though a temporary object MyClass(8) is involved in between which will be equated with y (fine no problem) but then it's reference will be sent to equate with z. Why doesn't the dangling reference problem occur here ? Why does the overload function work without const MyClass&' on the temporary object 'MyClass(8) ?

Temporaries live until the full expression ends. At the end the temporary goes away, but by then the assignments have all been performed and since you don't store the reference anywhere it's okay for the temporary to go away.

The first assignment MyClass(8) = y; returns a MyClass& that doesn't tell anything about being a temporary. And so it matches the parameter of the next operator=, whether that one expects a const or non-const parameter.
If you try to just assign a temporary z = MyClass(8);, you will see that it now requires the parameter to be a const reference.
Also, there are no dangling references here, because temporary objects live until the end of the full expression (usually at the ;), and no pointers or references are saved anywhere. Just copies of the num values.

Related

What is the meaning of assigning to return value of getter taking this pointer by value?

This doubt came to me when I jumped on an existing code and mistakenly used a getter to set a property,
obj.getProp() = otherProp;
instead of calling the setter,
obj.setProp(otherProp);
I did not realize the mistake because there was no error at compilation or runtime; the assignment resulted in a no-op.
So I came up with the following example, which outputs 337:
#include <iostream>
struct A {
int x = 0;
A(int x) : x(x) {}
A(A& a) : x(a.x) {}
void operator=(A const& other) { x = other.x; }
};
struct B {
A a{3};
int x{3};
A getAbyVal() { return a; }
A& getAbyRef() { return a; }
int getXbyVal() { return x; }
};
int main() {
B b;
std::cout << b.a.x; // this and the other two cout print what I expect, but...
b.getAbyVal() = A{7}; // ... I expected this to fail at compilation time in the first place...
//b.getXbyVal() = 3; // ... just like this fails.
std::cout << b.a.x;
b.getAbyRef() = A{7};
std::cout << b.a.x;
}
So my question is two folds:
what in b.getAbyVal() = A{7}; is different from b.getXbyVal() = 3; so that the former compiles and the latter doesn't (beside the fact that the types are A and int)?
changing void operator=(A const& other) { x = other.x; } to void operator=(A const& other) & { x = other.x; } makes b.getAbyVal() = A{7}; fail to compile. Why is this the case?
what in b.getAbyVal() = A{7}; is different from b.getXbyVal() = 3; so
that the former compiles and the latter doesn't (beside the fact that
the types are A and int)?
Surprisingly, the difference in types is exactly what makes one compile correctly, and other to fail.
A has an assignment operator defined for it, so compiler dutifully invokes it on the return value (only to discard the whole object later). But the code you wrote supports this. From the compiler view, some other interesting things might have happened in your assignment operator, despite the fact that the object will be eradicated (side effects in formal parlance).
With int as a return value, compiler knows there are no side effects of assigning value to int, so assigning any value to object which is to be eradicated immediately does not make any sense.
what in b.getAbyVal() = A{7}; is different from b.getXbyVal() = 3; so that the former compiles and the latter doesn't (beside the fact that the types are A and int)?
The difference is precisely that one functions returns a class type, and the other function returns a POD type. A temporary int, for example can't be assigned to:
42 = x; // error
so similarly the language disallows assigning to a temporary int returned from a function as well. This is not the default behavior for user-defined class types so assigning to a temporary A compiles:
A{} = x; // ok
changing void operator=(A const& other) { x = other.x; } to void operator=(A const& other) & { x = other.x; } makes b.getAbyVal() = A{7}; fail to compile. Why is this the case?
Adding a & at the end of is called a ref-qualifier, and allows a user-defined class to have the same semantics as a POD type when it comes to assigning to a temporary. Adding a & at the end of operator= constrains it to only be used on an l-value (basically, a named variable, or a reference returned from a function).
A{} = x; // now error
A a;
a = x; // still ok

operator+ overload error. Unable to return reference to object

There is the class:
class MyClass
{
private:
double value;
public:
MyClass()
{
this->value = 0;
}
MyClass(double value)
{
this->value = value;
}
MyClass& operator + (MyClass & outerObj);
};
Why does that overload work:
MyClass& MyClass::operator + (MyClass & outerObj)
{
MyClass retObject(this->value + outerObj.value);
return retObject;
}
But does this one not?
MyClass& MyClass::operator + (MyClass & outerObj)
{
return MyClass(this->value + outerObj.value);
}
VC++ issues a warning:
MyClass::MyClass(double value)
+3 overloads
initial value of reference to non-const must be an lvalue
I can not understand what the problem is. After all, it returns essentially the same thing.
The problem of the 1st code snippet, you're trying to return a reference being bound to local object. The local variable will be destroyed when get out of the function, then the returned reference is always dangled.
The problem of the 2nd code snippet, you're trying to bind a temporary object to lvalue-reference to non-const, which is ill-formed.
You should change the operator+ from return-by-reference to return-by-value, which will make both returning local object or temporary object work fine, and keep you away from the above troubles. e.g.
MyClass MyClass::operator + (const MyClass & outerObj) const {
...
}

Copy constructor is called instead of move constructor - why?

I have this code, taken from here by the way http://www.cplusplus.com/doc/tutorial/classes2/
// move constructor/assignment
#include <iostream>
#include <string>
#include <utility>
using namespace std;
class Example6
{
string* ptr;
public:
Example6(const string& str) :
ptr(new string(str))
{
cout << "DONT MOVE " << '\n';
}
~Example6()
{
delete ptr;
}
// move constructor
Example6(Example6&& x) :
ptr(x.ptr)
{
cout << "MOVE " << '\n';
x.ptr = nullptr;
}
// move assignment
Example6& operator=(Example6&& x)
{
delete ptr;
ptr = x.ptr;
x.ptr = nullptr;
return *this;
}
// access content:
const string& content() const
{
return *ptr;
}
// addition:
Example6 operator+(const Example6& rhs)
{
return Example6(content() + rhs.content());
}
};
int main()
{
Example6 foo("Exam");
Example6 bar = Example6("ple"); // move-construction
foo = foo + bar; // move-assignment
cout << "foo's content: " << foo.content() << '\n';
return 0;
}
I only added output in constructor to see which is being called. To my surprise it is always the first one, copy constructor. Why does it happen? I did some research and found some info about elision. Is it somehow possible to prevent it and always call move constructor?
Also, as a side note, as I said this code is from cplusplus.com. However, I read about move semantics in some other places and I wonder if this move constructor here is done right. Shouldn't it call
ptr(move(x.ptr))
instead of just
ptr(x.ptr)
The way I understand this, if we use the second option, then we are calling copy constructor of string, instead of move, because x is rvalue reference that has a name, so it is really lvalue and we need to use move to cast it to be rvalue. Do i miss something, or is it really tutorial's mistake?
Btw, adding move doesn't solve my first problem.
So anything with a name is an lvalue.
An rvalue reference with a name is an lvalue.
An rvalue reference will bind to rvalues, but it itself is an lvalue.
So x in ptr(x.ptr) is an rvalue reference, but it has a name, so it is an lvalue.
To treat it as an rvalue, you need to do ptr( std::move(x).ptr ).
Of course, this is mostly useless, as moving a ptr does nothing as ptr is a dumb raw pointer.
You should be following the rule of 0 here.
class Example6 {
std::unique_ptr<string> ptr;
public:
Example6 (string str) : ptr(std::make_unique<string>(std::move(str))) {cout << "DONT MOVE " << '\n';}
Example6():Example6("") {}
~Example6 () = default;
// move constructor
Example6 (Example6&& x) = default;
// move assignment
Example6& operator= (Example6&& x) = default;
// access content:
const string& content() const {
if (!ptr) *this=Example6{};
return *ptr;
}
// addition:
Example6 operator+(const Example6& rhs) {
return Example6(content()+rhs.content());
}
};
because business logic and lifetime management don't belong intermixed in the same class.
While we are at it:
// addition:
Example6& operator+=(const Example6& rhs) & {
if (!ptr) *this = Example6{};
*ptr += rhs.content();
return *this;
}
// addition:
friend Example6 operator+(Example6 lhs, const Example6& rhs) {
lhs += rhs;
return lhs;
}
Copy constructor is called ... - why?
The premise of your question is faulty: The copy constructor is not called. In fact, the class is not copyable.
The first constructor is a converting constructor from std::string. The converting constructor is called because Example6 objects are initialised with a string argument. Once in each of these expressions:
Example6 foo("Exam")
Example6("ple")
Example6(content() + rhs.content()
... instead of move constructor
There are a few copy-initialisations by move in the program. However, all of them can be elided by the compiler.
Is it somehow possible to prevent it and always call move constructor?
There are a few mistakes that can prevent copy elision. For example, if you wrote the addition operator like this:
return std::move(Example6(content()+rhs.content()));
The compiler would fail to elide the move and probably tell you about it if you're lucky:
warning: moving a temporary object prevents copy elision
Shouldn't it call
ptr(move(x.ptr))
instead of just
ptr(x.ptr)
There's no need. Moving a pointer is exactly the same as copying a pointer. Same holds for all fundamental types.
The way I understand this, if we use the second option, then we are calling copy constructor of string, instead of move
ptr is not a string. It is a pointer to a string. Copying a pointer does nothing to the pointed object.
PS. The example program is quite bad quality. There should never be owning bare pointers in C++.
I can say your class does not have a copy constructor.
Because copy ctor parameter have to be const and reference
class Example6{
public:
Example6(const Example6 &r);
};

How does return by reference work in C++ classes?

In C++, if i type:
int x=5;
int &y=x;
then y will act as an alias for the memory location where original x is stored and this can be proven/tested by printing the memory location of both, x and y
the output of a similar program is below:
x is at location: 0x23fe14
y is at location: 0x23fe14
But what about classes?
when a member function is declared with return type as a reference and the function uses the this pointer, what is the function actually returning?
for example:
#include <iostream>
class simple
{
int data;
public:
// ctor
simple():
data(0)
{}
// getter function
int& getter_data()
{ return this->data; }
// modifier functions
simple& add(int x=5)
{ this->data += x;
return *this;
}
simple& sub(int x=5)
{ this->data -= x;
return *this;
}
};
int main()
{ simple obj;
obj.add().sub(4); ////////// how & why is it working? /////////
std::cout<<obj.getter_data();
getchar();
}
why is it possible to execute the command in highlighted line?
what kind of data is obj.add() returning to the sub()?
When your member-function returns simple& initialized with *this (*this being an instance of simple) the semantics are the same as your own "reference example"; a reference is initialized with an instance of that type.
You are initializing the returned reference with the object itself.
The below snippets are semantically equivalent:
obj.add ().sub (4);
simple& ref = obj.add ();
ref.sub (4); // `ref` is really `obj`,
// the address of `ref` and `obj` are the same
what kind of data is obj.add() returning to the sub()?
obj.add() is returning a reference to the obj instance.
The text "returning to the sub()" is not making sense at all.
However, in your case, the reference to your object is very similar to a plain pointer to your object, and often it is a good metapher.

Overloading Based on L-Value versus R-Value

I found in a C++ book the following:
Although we will not be doing it in this book, you can overload a
function name (or operator) so that it behaves differently when used
as an l-value and when it is used as an r-value. (Recall that an
l-value means it can be used on the left-hand side of an assignment
statement.) For example, if you want a function f to behave
differently depending on whether it is used as an l-value or an
r-value, you can do so as follows:
class SomeClass {
public:
int& f(); // will be used in any l-value invocation const
const int& f( ) const; // used in any r-value invocation ...
};
I tried this and it didn't work:
class Foo {
public:
int& id(int& a);
const int& id(int& a) const;
};
int main() {
int a;
Foo f;
f.id(a) = 2;
a = f.id(a);
cout << f.id(a) << endl;
}
int& Foo :: id(int& a) {
cout << "Bar\n";
return a;
}
const int& Foo :: id(int& a) const {
cout << "No bar !\n";
return a;
}
Have I wrongly understood it ?
Either the book's example is flat-out wrong, or you copied the wrong example from the book.
class SomeClass {
public:
int& f(); // will be used in any l-value invocation const
const int& f( ) const; // used in any r-value invocation ...
};
With this code, when you call s.f() where s is an object of type SomeClass, the first version will be called when s is non-const, and the second version will be called when s is const. Value category has nothing to do with it.
Ref-qualification looks like this:
#include <iostream>
class SomeClass {
public:
int f() & { std::cout << "lvalue\n"; }
int f() && { std::cout << "rvalue\n"; }
};
int main() {
SomeClass s; s.f(); // prints "lvalue"
SomeClass{}.f(); // prints "rvalue"
}
Ofcourse the book is correct. Let me explain the workings of an example of what the author meant :
#include <iostream>
using namespace std;
class CO
{
int _m;
public:
CO(int m) : _m(m) {}
int& m() { return _m; } // used as an l-value
int const& m() const { return _m; } // used as an r-value
};
int main()
{
CO a(1);
cout << a.m() << endl;
a.m() = 2; // here used as an l-value / overload resolution selects the correct one
cout << a.m() << endl;
return 0;
}
Output is
1
2
What you misunderstood is the function signature. You see when you have an argument &arg (as in id(&arg)) you pretty much predefine the l-valuness of it, so returning it through a const or non const member function does not change a thing.
The author refers to a common writting style that allows for 'getters' and 'setters' to be declared with a signature different only in const qualifires yet compile and behave correctly.
Edit
To be more pedantic, the following phrase
Recall that an l-value means it can be used on the left-hand side of an assignment statement.
is not valid anymore. lr valuness applies to expressions, and the shortest way to explain it, is that an expression whose adress we can take, is an l-value; if it's not obtainable it's an r-value.
So the syntax to which the author refers to, enforces the member function to be used correctly (correct compilation / overload resolution) at both sides of the assignment operator. This nowdays is no longer relevant to lr valueness.
A const member function can only be called on a const object. It makes no difference what you do with the return value. In your example, f is non-const, so it always calls the non-const version of f(). Note that you can also overload on r-value references (&&) in C++11.