Home c++ Overloading the C++ assignment operator

Overloading the C++ assignment operator

Author

Date

Category

I can’t figure out how to overload the assignment operator. With binary operators, everything is more or less clear, there are at least two operands, but this is not the case. Could you give an example of “=” overloading, and clarify what it does where, and the result of the overload? For example, overload so that it adds + 5 to the assigned number, or something like that, and show to what and from what it is assigned and to what where it is added.


Answer 1, authority 100%

Vector & amp; Vector :: operator = (Vector & amp; v) // overload
{
 x = v.x; y = v.y; z = v.z;
 return * this; // return a reference to the current object
}

Answer 2, authority 73%

A trendy C++ 11-style option using swap and carry semantics:

class C
{
 public:
  C (std :: string someName): name (someName) {}
  void swap (C & amp; other)
  {
    name.swap (other.name);
  }
  C (const C & amp; other): name (other.name) // copy constructor from lvalue
  {
  }
  C (C & amp; & amp; other) // copy constructor from rvalue, aka carry constructor
  {
    this- & gt; swap (other);
  }
  C & amp; operator = (C other) // assignment operator
  {// passing a parameter by value is important!
    this- & gt; swap (other); // exchange with temporary copy
    return * this;
  }
 private:
  std :: string name;
};

(there are no comments, I’m transferring here)

@ avp :
IMHO the further, the C++ becomes more incomprehensible.

Apparently from a language for practical programming, it will soon turn into a language for a certain caste of hackers who make tools for making tools.

The question here is how many ordinary programmers will be able to at least use (not to mention modification) the final tools they create.


@VladD :
C++ gets complicated, that’s a fact. In addition, there are also native lambdas for the language and a lot of everything.

From my practice, the object shallow semantics (kak eto po-russki?) that are generated by default for C++ objects are almost never needed. IMHO the default noncopyable semantics would be much better.


@ avp :
@VladD , after replacement

C (C & amp; & amp; other)

to

C (C & amp; other)

succeeded

avp @ avp-xub11: ~ / src / dispro $ g ++ -std = C++ 0x t.cpp
avp @ avp-xub11: ~ / src / dispro $ ./a.out
swap, other = (0xbfce8970)
c1 - & gt;
c2 - & gt; c1
avp @ avp-xub11: ~ / src / dispro $

Was that intended? I don’t see any sense here.

By the way, the question was about the = operator.


@VladD :
@avp : No, it was different. Should be

C (const C & amp; other): name (other.name) // copy constructor from lvalue
{
}

(this is my answer) Should work correctly.

As for the assignment operator, in the code, copying is performed first, other in the operator is already a copy. This means that you can “steal” the value from it and leave it to die at the end of the function. The idea is that copying is still unavoidable, and transfer is a cheap operation.


@ avp :
@VladD , I get it. Corrected the constructor as in the answer and it worked.

I wonder if swap is used for efficiency? Is it just an exchange of pointers?

All this is so deeply buried that you just want to give up on these innovations and not hire those who write like that.


@VladD :
@avp : the idea is that swap is written once, and the rest of the functions / operators use it whenever possible. This reduces the repetition of the code. Plus swap should not give exceptions, EMNIP, with the resulting exception-safety.


@ avp :
@VladD , it is also clear about swap once. Its meaning is not entirely clear.

Look, is the string passed by value to the constructor? If so, the bytes are copied anyway. Then an exchange of pointers with a temporary copy is carried out, and obviously someday a destructor will be called at the temporary copy. So?

Why is it better than passing a link to the constructor and copying bytes in it?

Or am I missing something fundamental?


@VladD :
@avp : no better, just think less: all operations are standardly expressed through swap. Look: old data needs to be deallocated. We trust this to the temporary copy destructor, not write it ourselves. Likewise, in the assignment statement, we trust the copy constructor to create a copy, rather than write ourselves.


@ avp :
@VladD , why deallocate something when transferring by reference?

IMHO passing by value simply adds extra calls to the constructor and destructor of the temporary copy.

Am I wrong somewhere?

Some strange approach.



Answer 3, authority 27%

Many years ago I found such a universal option: upon assignment, call the destructor of the old object and initialize this place with a new object.

# include & lt; iostream & gt;
class T {
  int x;
public:
  T (int _x = 0): x (_x) {
    std :: cerr & lt; & lt; "T (int" & lt; & lt; x & lt; & lt; ") \ n";
  }
  T (const T & amp; t): x (t.x) {
    std :: cerr & lt; & lt; "T (const & amp; T" & lt; & lt; x & lt; & lt; ") \ n";
  }
  ~ T () {
    std :: cerr & lt; & lt; "~ T (" & lt; & lt; x & lt; & lt; ") \ n";
  }
  const T & amp; operator = (const T & amp; t) {
    if (this! = & amp; t) {
      this- & gt; ~ T ();
      new (this) T (t);
    }
    return * this;
  }
  friend class C;
};
class C {
  T x;
public:
  C (T _x = 0): x (_x) {
    std :: cerr & lt; & lt; "C (T" & lt; & lt; x.x & lt; & lt; ") \ n";
  }
  C (const C & amp; c): x (c.x) {
    std :: cerr & lt; & lt; "C (const & amp; C" & lt; & lt; x.x & lt; & lt; ") \ n";
  }
  ~ C () {
    std :: cerr & lt; & lt; "~ C (" & lt; & lt; x.x & lt; & lt; ") \ n";
  }
};
int main ()
{
std :: cerr & lt; & lt; __ LINE __ & lt; & lt; '\ n';
  T a (1), b (2);
  std :: cerr & lt; & lt; __ LINE __ & lt; & lt; '\ n';
  C x (a), y (b);
  std :: cerr & lt; & lt; __ LINE __ & lt; & lt; '\ n';
  a = b;
  std :: cerr & lt; & lt; __ LINE __ & lt; & lt; '\ n';
  x = y;
  std :: cerr & lt; & lt; __ LINE __ & lt; & lt; '\ n';
}

running this example (using gcc):

38
T (int 1)
T (int 2)
40
T (const & amp; T 1)
T (const & amp; T 1)
C (T 1)
~ T (1)
T (const & amp; T 2)
T (const & amp; T 2)
C (T 2)
~ T (2)
42
~ T (1)
T (const & amp; T 2)
44
~ T (1)
T (const & amp; T 2)
46
~ C (2)
~ T (2)
~ C (2)
~ T (2)
~ T (2)
~ T (2)

Line 41: The T constructor is called first to pass the argument to the C constructor, where the object is initialized to the class member, then the destructor for the argument is called. If you put “const T & amp; _x” instead of “T _x” in the C constructor, you can avoid unnecessary calls to the constructor and destructor.

Line 43: Our assignment operator for T worked


Answer 4, authority 9%

The smallest assignment operator is

void Cls :: operator = (Cls other) {
 swap (* this, other);
}

According to the standard, this is the copy assignment operator.
However, it can also do move if Cls has a move constructor:

Cls a, b;
a = std :: move (b);
// Works like
// Cls other (std :: move (b)); a.operator = (other);
// ^^^^^^^^^^
// move: call Cls :: Cls (Cls & amp; & amp;)

After swap, the current members of the class appear in the temporary other object and are removed when the assignment statement is exited.
A copy assignment to itself will make an extra copy, but no errors.

The result type can be anything.
The automatically generated assignment operator has a return type of Cls & amp; and returns * this .
This allows you to write code like a = b = c or (a = b) & gt; c .
But many coding conventions do not favor this, in particular see CppCoreGuidelines ES.expr “Avoid complicated expressions” .

This assignment operator requires copy / move constructors and a swap function to work.
Together it looks like this:

class Cls {
public:
 Cls () {}
 // Copy constructor
 Cls (const Cls & amp; other): x (other.x), y (other.y) {}
 // Move constructor
 Cls (Cls & amp; & amp; other) noexcept {
  swap (* this, other);
 }
 // Assignment operator
 void operator = (Cls other) noexcept {
  swap (* this, other);
 }
 // Exchange
 friend void swap (Cls & amp; a, Cls & amp; b) noexcept {
  using std :: swap; // Add a standard function to the overload list ...
  swap (a.x, b.x); // ... and invoked using search by argument types (ADL).
  swap (a.y, b.y);
 }
private:
 X x;
 Y y;
};

The copy constructor copies each member of the class.

The move constructor constructs empty class members and then exchanges them with its argument. You can move each member individually, but it is more convenient to use swap .

The swap function can be a free friend function. Many algorithms expect a free swap function and call it through an Argument Type Lookup (ADL).
Previously, it was recommended to also write a method swap so that you can write f (). Swap (x); , but with the advent of move semantics, this is no longer you need.

If functions cannot throw exceptions, then they should be marked as noexcept . This is needed for std :: move_if_noexcept and other functions that can use more efficient code if the assignment or constructor does not throw exceptions. For such a class, & lt; type_traits & gt; produces

std :: is_nothrow_copy_constructible & lt; Cls & gt; == 0
std :: is_nothrow_move_constructible & lt; Cls & gt; == 1
std :: is_nothrow_copy_assignable & lt; Cls & gt; == 0
std :: is_nothrow_move_assignable & lt; Cls & gt; == 1

Although the assignment operator is marked as noexcept , when called with the const Cls & amp; argument, copying will occur, which may throw an exception. Therefore is_nothrow_copy_assignable returns false .

Programmers, Start Your Engines!

Why spend time searching for the correct question and then entering your answer when you can find it in a second? That's what CompuTicket is all about! Here you'll find thousands of questions and answers from hundreds of computer languages.

Recent questions