• Home   /  
  • Archive by category "1"

Overloaded Assignment Operator C++ Const Method

C++ Operator Overloading Guidelines

One of the nice features of C++ is that you can give special meanings to operators, when they are used with user-defined classes. This is called operator overloading. You can implement C++ operator overloads by providing special member-functions on your classes that follow a particular naming convention. For example, to overload the operator for your class, you would provide a member-function named on your class.

The following set of operators is commonly overloaded for user-defined classes:

  • (assignment operator)
  • (binary arithmetic operators)
  • (compound assignment operators)
  • (comparison operators)

Here are some guidelines for implementing these operators. These guidelines are very important to follow, so definitely get in the habit early.

Assignment Operator

The assignment operator has a signature like this:

class MyClass { public: ... MyClass & operator=(const MyClass &rhs); ... } MyClass a, b; ... b = a; // Same as b.operator=(a);

Notice that the operator takes a const-reference to the right hand side of the assignment. The reason for this should be obvious, since we don't want to change that value; we only want to change what's on the left hand side.

Also, you will notice that a reference is returned by the assignment operator. This is to allow operator chaining. You typically see it with primitive types, like this:

int a, b, c, d, e; a = b = c = d = e = 42; This is interpreted by the compiler as: a = (b = (c = (d = (e = 42)))); In other words, assignment is right-associative. The last assignment operation is evaluated first, and is propagated leftward through the series of assignments. Specifically:
  • assigns 42 to , then returns as the result
  • The value of is then assigned to , and then is returned as the result
  • The value of is then assigned to , and then is returned as the result
  • etc.

Now, in order to support operator chaining, the assignment operator must return some value. The value that should be returned is a reference to the left-hand side of the assignment.

Notice that the returned reference is not declared . This can be a bit confusing, because it allows you to write crazy stuff like this:

MyClass a, b, c; ... (a = b) = c; // What?? At first glance, you might want to prevent situations like this, by having return a reference. However, statements like this will work with primitive types. And, even worse, some tools actually rely on this behavior. Therefore, it is important to return a non- reference from your . The rule of thumb is, "If it's good enough for s, it's good enough for user-defined data-types."

So, for the hypothetical assignment operator, you would do something like this:

// Take a const-reference to the right-hand side of the assignment. // Return a non-const reference to the left-hand side. MyClass& MyClass::operator=(const MyClass &rhs) { ... // Do the assignment operation! return *this; // Return a reference to myself. } Remember, is a pointer to the object that the member function is being called on. Since is treated as , you can see why it makes sense to return the object that the function is called on; object is the left-hand side.

But, the member function needs to return a reference to the object, not a pointer to the object. So, it returns , which returns what points at (i.e. the object), not the pointer itself. (In C++, instances are turned into references, and vice versa, pretty much automatically, so even though is an instance, C++ implicitly converts it into a reference to the instance.)

Now, one more very important point about the assignment operator:

YOU MUST CHECK FOR SELF-ASSIGNMENT!

This is especially important when your class does its own memory allocation. Here is why: The typical sequence of operations within an assignment operator is usually something like this:

MyClass& MyClass::operator=(const MyClass &rhs) { // 1. Deallocate any memory that MyClass is using internally // 2. Allocate some memory to hold the contents of rhs // 3. Copy the values from rhs into this instance // 4. Return *this } Now, what happens when you do something like this: MyClass mc; ... mc = mc; // BLAMMO. You can hopefully see that this would wreak havoc on your program. Because is on the left-hand side and on the right-hand side, the first thing that happens is that releases any memory it holds internally. But, this is where the values were going to be copied from, since is also on the right-hand side! So, you can see that this completely messes up the rest of the assignment operator's internals.

The easy way to avoid this is to CHECK FOR SELF-ASSIGNMENT. There are many ways to answer the question, "Are these two instances the same?" But, for our purposes, just compare the two objects' addresses. If they are the same, then don't do assignment. If they are different, then do the assignment.

So, the correct and safe version of the assignment operator would be this:

MyClass& MyClass::operator=(const MyClass &rhs) { // Check for self-assignment! if (this == &rhs) // Same object? return *this; // Yes, so skip assignment, and just return *this. ... // Deallocate, allocate new space, copy values... return *this; } Or, you can simplify this a bit by doing: MyClass& MyClass::operator=(const MyClass &rhs) { // Only do assignment if RHS is a different object from this. if (this != &rhs) { ... // Deallocate, allocate new space, copy values... } return *this; } Remember that in the comparison, is a pointer to the object being called, and is a pointer to the object being passed in as the argument. So, you can see that we avoid the dangers of self-assignment with this check.

In summary, the guidelines for the assignment operator are:

  1. Take a const-reference for the argument (the right-hand side of the assignment).
  2. Return a reference to the left-hand side, to support safe and reasonable operator chaining. (Do this by returning .)
  3. Check for self-assignment, by comparing the pointers ( to ).

Compound Assignment Operators

I discuss these before the arithmetic operators for a very specific reason, but we will get to that in a moment. The important point is that these are destructive operators, because they update or replace the values on the left-hand side of the assignment. So, you write:

MyClass a, b; ... a += b; // Same as a.operator+=(b) In this case, the values within are modified by the operator.

How those values are modified isn't very important - obviously, what represents will dictate what these operators mean.

The member function signature for such an operator should be like this:

MyClass & MyClass::operator+=(const MyClass &rhs) { ... } We have already covered the reason why is a const-reference. And, the implementation of such an operation should also be straightforward.

But, you will notice that the operator returns a -reference, and a non-const one at that. This is so you can do things like this:

MyClass mc; ... (mc += 5) += 3;

Don't ask me why somebody would want to do this, but just like the normal assignment operator, this is allowed by the primitive data types. Our user-defined datatypes should match the same general characteristics of the primitive data types when it comes to operators, to make sure that everything works as expected.

This is very straightforward to do. Just write your compound assignment operator implementation, and return at the end, just like for the regular assignment operator. So, you would end up with something like this:

MyClass & MyClass::operator+=(const MyClass &rhs) { ... // Do the compound assignment work. return *this; }

As one last note, in general you should beware of self-assignment with compound assignment operators as well. Fortunately, none of the C++ track's labs require you to worry about this, but you should always give it some thought when you are working on your own classes.

Binary Arithmetic Operators

The binary arithmetic operators are interesting because they don't modify either operand - they actually return a new value from the two arguments. You might think this is going to be an annoying bit of extra work, but here is the secret:

Define your binary arithmetic operators using your compound assignment operators.

There, I just saved you a bunch of time on your homeworks.

So, you have implemented your operator, and now you want to implement the operator. The function signature should be like this:

// Add this instance's value to other, and return a new instance // with the result. const MyClass MyClass::operator+(const MyClass &other) const { MyClass result = *this; // Make a copy of myself. Same as MyClass result(*this); result += other; // Use += to add other to the copy. return result; // All done! } Simple!

Actually, this explicitly spells out all of the steps, and if you want, you can combine them all into a single statement, like so:

// Add this instance's value to other, and return a new instance // with the result. const MyClass MyClass::operator+(const MyClass &other) const { return MyClass(*this) += other; } This creates an unnamed instance of , which is a copy of . Then, the operator is called on the temporary value, and then returns it.

If that last statement doesn't make sense to you yet, then stick with the other way, which spells out all of the steps. But, if you understand exactly what is going on, then you can use that approach.

You will notice that the operator returns a instance, not a reference. This is so that people can't write strange statements like this:

MyClass a, b, c; ... (a + b) = c; // Wuh...? This statement would basically do nothing, but if the operator returns a non- value, it will compile! So, we want to return a instance, so that such madness will not even be allowed to compile.

To summarize, the guidelines for the binary arithmetic operators are:

  1. Implement the compound assignment operators from scratch, and then define the binary arithmetic operators in terms of the corresponding compound assignment operators.
  2. Return a instance, to prevent worthless and confusing assignment operations that shouldn't be allowed.

Comparison Operators and

The comparison operators are very simple. Define first, using a function signature like this:

bool MyClass::operator==(const MyClass &other) const { ... // Compare the values, and return a bool result. } The internals are very obvious and straightforward, and the return-value is also very obvious.

The important point here is that the operator can also be defined in terms of the operator, and you should do this to save effort. You can do something like this:

bool MyClass::operator!=(const MyClass &other) const { return !(*this == other); } That way you get to reuse the hard work you did on implementing your operator. Also, your code is far less likely to exhibit inconsistencies between and , since one is implemented in terms of the other.
Updated Oct 23, 2007
Copyright (c) 2005-2007, California Institute of Technology.

A copy assignment operator of class is a non-template non-static member function with the name operator= that takes exactly one parameter of type T, T&, const T&, volatile T&, or constvolatile T&. For a type to be , it must have a public copy assignment operator.

[edit]Syntax

class_nameclass_name ( class_name ) (1)
class_nameclass_name ( const class_name ) (2)
class_nameclass_name ( const class_name ) = default; (3) (since C++11)
class_nameclass_name ( const class_name ) = delete; (4) (since C++11)

[edit]Explanation

  1. Typical declaration of a copy assignment operator when copy-and-swap idiom can be used.
  2. Typical declaration of a copy assignment operator when copy-and-swap idiom cannot be used (non-swappable type or degraded performance).
  3. Forcing a copy assignment operator to be generated by the compiler.
  4. Avoiding implicit copy assignment.

The copy assignment operator is called whenever selected by overload resolution, e.g. when an object appears on the left side of an assignment expression.

[edit]Implicitly-declared copy assignment operator

If no user-defined copy assignment operators are provided for a class type (struct, class, or union), the compiler will always declare one as an inline public member of the class. This implicitly-declared copy assignment operator has the form T& T::operator=(const T&) if all of the following is true:

  • each direct base of has a copy assignment operator whose parameters are B or const B& or constvolatile B&;
  • each non-static data member of of class type or array of class type has a copy assignment operator whose parameters are M or const M& or constvolatile M&.

Otherwise the implicitly-declared copy assignment operator is declared as T& T::operator=(T&). (Note that due to these rules, the implicitly-declared copy assignment operator cannot bind to a volatile lvalue argument.)

A class can have multiple copy assignment operators, e.g. both T& T::operator=(const T&) and T& T::operator=(T). If some user-defined copy assignment operators are present, the user may still force the generation of the implicitly declared copy assignment operator with the keyword .(since C++11)

The implicitly-declared (or defaulted on its first declaration) copy assignment operator has an exception specification as described in dynamic exception specification(until C++17)exception specification(since C++17)

Because the copy assignment operator is always declared for any class, the base class assignment operator is always hidden. If a using-declaration is used to bring in the assignment operator from the base class, and its argument type could be the same as the argument type of the implicit assignment operator of the derived class, the using-declaration is also hidden by the implicit declaration.

[edit]Deleted implicitly-declared copy assignment operator

A implicitly-declared copy assignment operator for class is defined as deleted if any of the following is true:

  • has a user-declared move constructor;
  • has a user-declared move assignment operator.

Otherwise, it is defined as defaulted.

A defaulted copy assignment operator for class is defined as deleted if any of the following is true:

  • has a non-static data member of non-class type (or array thereof) that is const;
  • has a non-static data member of a reference type;
  • has a non-static data member or a direct or virtual base class that cannot be copy-assigned (overload resolution for the copy assignment fails, or selects a deleted or inaccessible function);
  • is a union-like class, and has a variant member whose corresponding assignment operator is non-trivial.

[edit]Trivial copy assignment operator

The copy assignment operator for class is trivial if all of the following is true:

  • it is not user-provided (meaning, it is implicitly-defined or defaulted) , , and if it is defaulted, its signature is the same as implicitly-defined(until C++14);
  • has no virtual member functions;
  • has no virtual base classes;
  • the copy assignment operator selected for every direct base of is trivial;
  • the copy assignment operator selected for every non-static class type (or array of class type) member of is trivial;
  • has no non-static data members of volatile-qualified type.
(since C++14)

A trivial copy assignment operator makes a copy of the object representation as if by std::memmove. All data types compatible with the C language (POD types) are trivially copy-assignable.

[edit]Implicitly-defined copy assignment operator

If the implicitly-declared copy assignment operator is neither deleted nor trivial, it is defined (that is, a function body is generated and compiled) by the compiler if odr-used. For union types, the implicitly-defined copy assignment copies the object representation (as by std::memmove). For non-union class types (class and struct), the operator performs member-wise copy assignment of the object's bases and non-static members, in their initialization order, using built-in assignment for the scalars and copy assignment operator for class types.

The generation of the implicitly-defined copy assignment operator is deprecated(since C++11) if has a user-declared destructor or user-declared copy constructor.

[edit]Notes

If both copy and move assignment operators are provided, overload resolution selects the move assignment if the argument is an rvalue (either a prvalue such as a nameless temporary or an xvalue such as the result of std::move), and selects the copy assignment if the argument is an lvalue (named object or a function/operator returning lvalue reference). If only the copy assignment is provided, all argument categories select it (as long as it takes its argument by value or as reference to const, since rvalues can bind to const references), which makes copy assignment the fallback for move assignment, when move is unavailable.

It is unspecified whether virtual base class subobjects that are accessible through more than one path in the inheritance lattice, are assigned more than once by the implicitly-defined copy assignment operator (same applies to move assignment).

See assignment operator overloading for additional detail on the expected behavior of a user-defined copy-assignment operator.

[edit]Example

Run this code

Output:

#include <iostream>#include <memory>#include <string>#include <algorithm>   struct A {int n;std::string s1;// user-defined copy assignment, copy-and-swap form A& operator=(A other){std::cout<<"copy assignment of A\n";std::swap(n, other.n);std::swap(s1, other.s1);return*this;}};   struct B : A {std::string s2;// implicitly-defined copy assignment};   struct C {std::unique_ptr<int[]> data;std::size_t size;// non-copy-and-swap assignment C& operator=(const C& other){// check for self-assignmentif(&other == this)return*this;// reuse storage when possibleif(size != other.size){ data.reset(new int[other.size]); size = other.size;}std::copy(&other.data[0], &other.data[0]+ size, &data[0]);return*this;}// note: copy-and-swap would always cause a reallocation};   int main(){ A a1, a2;std::cout<<"a1 = a2 calls "; a1 = a2;// user-defined copy assignment   B b1, b2; b2.s1="foo"; b2.s2="bar";std::cout<<"b1 = b2 calls "; b1 = b2;// implicitly-defined copy assignmentstd::cout<<"b1.s1 = "<< b1.s1<<" b1.s2 = "<< b1.s2<<'\n';}
a1 = a2 calls copy assignment of A b1 = b2 calls copy assignment of A b1.s1 = foo b1.s2 = bar

[edit]Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
CWG 2171 C++14 operator=(X&)=default was non-trivial made trivial

One thought on “Overloaded Assignment Operator C++ Const Method

Leave a comment

L'indirizzo email non verrĂ  pubblicato. I campi obbligatori sono contrassegnati *