• Home   /  
  • Archive by category "1"

Overload Assignment Operator C++ Struct Member

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

When working with arrays, we typically use the subscript operator ([]) to index specific elements of an array:

However, consider the following IntList class, which has a member variable that is an array:

Because the m_list member variable is private, we can not access it directly from variable list. This means we have no way to directly get or set values in the m_list array. So how do we get or put elements into our list?

Without operator overloading, the typical method would be to create access functions:

While this works, it’s not particularly user friendly. Consider the following example:

Are we setting element 2 to the value 3, or element 3 to the value 2? Without seeing the definition of setItem(), it’s simply not clear.

You could also just return the entire list and use operator[] to access the element:

While this also works, it’s syntactically odd:

Overloading operator[]

However, a better solution in this case is to overload the subscript operator ([]) to allow access to the elements of m_list. The subscript operator is one of the operators that must be overloaded as a member function. An overloaded operator[] function will always take one parameter: the subscript that the user places between the hard braces. In our IntList case, we expect the user to pass in an integer index, and we’ll return an integer value back as a result.

Now, whenever we use the subscript operator ([]) on an object of our class, the compiler will return the corresponding element from the m_list member variable! This allows us to both get and set values of m_list directly:

This is both easy syntactically and from a comprehension standpoint. When evaluates, the compiler first checks to see if there’s an overloaded operator[] function. If so, it passes the value inside the hard braces (in this case, 2) as an argument to the function.

Note that although you can provide a default value for the function parameter, actually using operator[] without a subscript inside is not considered a valid syntax, so there’s no point.

Why operator[] returns a reference

Let’s take a closer look at how evaluates. Because the subscript operator has a higher precedence than the assignment operator, evaluates first. calls operator[], which we’ve defined to return a reference to . Because operator[] is returning a reference, it returns the actual array element. Our partially evaluated expression becomes , which is a straightforward integer assignment.

In the lesson a first look at variables, you learned that any value on the left hand side of an assignment statement must be an l-value (which is a variable that has an actual memory address). Because the result of operator[] can be used on the left hand side of an assignment (e.g. ), the return value of operator[] must be an l-value. As it turns out, references are always l-values, because you can only take a reference of variables that have memory addresses. So by returning a reference, the compiler is satisfied that we are returning an l-value.

Consider what would happen if operator[] returned an integer by value instead of by reference. would call operator[], which would return the value of list.m_list[2]. For example, if m_list[2] had the value of 6, operator[] would return the value 6. would partially evaluate to , which makes no sense! If you try to do this, the C++ compiler will complain:

C:VCProjectsTest.cpp(386) : error C2106: '=' : left operand must be l-value

Dealing with const objects

In the above IntList example, operator[] is non-const, and we can use it as an l-value to change the state of non-const objects. However, what if our IntList object was const? In this case, we wouldn’t be able to call the non-const version of operator[] because that would allow us to potentially change the state of a const object.

The good news is that we can define a non-const and a const version of operator[] separately. The non-const version will be used with non-const objects, and the const version with const-objects.

If we comment out the line , the above program compiles and executes as expected.

Error checking

One other advantage of overloading the subscript operator is that we can make it safer than accessing arrays directly. Normally, when accessing arrays, the subscript operator does not check whether the index is valid. For example, the compiler will not complain about the following code:

However, if we know the size of our array, we can make our overloaded subscript operator check to ensure the index is within bounds:

In the above example, we have used the assert() function (included in the cassert header) to make sure our index is valid. If the expression inside the assert evaluates to false (which means the user passed in an invalid index), the program will terminate with an error message, which is much better than the alternative (corrupting memory). This is probably the most common method of doing error checking of this sort.

Pointers to objects and overloaded operator[] don’t mix

If you try to call operator[] on a pointer to an object, C++ will assume you’re trying to index an array of objects of that type.

Consider the following example:

Because we can’t assign an integer to an IntList, this won’t compile. However, if assigning an integer was valid, this would compile and run, with undefined results.

Rule: Make sure you’re not trying to call an overloaded operator[] on a pointer to an object.

The proper syntax would be to dereference the pointer first (making sure to use parenthesis since operator[] has higher precedence than operator*), then call operator[]:

This is ugly and error prone. Better yet, don’t set pointers to your objects if you don’t have to.

The function parameter does not need to be an integer

As mentioned above, C++ passes what the user types between the hard braces as an argument to the overloaded function. In most cases, this will be an integer value. However, this is not required -- and in fact, you can define that your overloaded operator[] take a value of any type you desire. You could define your overloaded operator[] to take a double, a std::string, or whatever else you like.

As a ridiculous example, just so you can see that it works:

As you would expect, this prints:

Hello, world!

Overloading operator[] to take a std::string parameter can be useful when writing certain kinds of classes, such as those that use words as indices.

Conclusion

The subscript operator is typically overloaded to provide direct access to individual elements from an array (or other similar structure) contained within a class. Because strings are often implemented as arrays of characters, operator[] is often implemented in string classes to allow the user to access a single character of the string.

Quiz time

1) A map is a class that stores elements as a key-value pair. The key must be unique, and is used to access the associated pair. In this quiz, we’re going to write an application that lets us assign grades to students by name, using a simple map class. The student’s name will be the key, and the grade (as a char) will be the value.

1a) First, write a struct named StudentGrade that contains the student’s name (as a std::string) and grade (as a char).

Show Solution

1b) Add a class named GradeMap that contains a std::vector of StudentGrade named m_map. Add a default constructor that does nothing.

Show Solution

1c) Write an overloaded operator[] for this class. This function should take a std::string parameter, and return a reference to a char. In the body of the function, first iterate through the vector to see if the student’s name already exists (you can use a for-each loop for this). If the student exists, return a reference to the grade and you’re done. Otherwise, use the std::vector::push_back() function to add a StudentGrade for this new student. When you do this, std::vector will add a copy of your StudentGrade to itself (resizing if needed). Finally, we need to return a reference to the grade for the student we just added to the std::vector. We can access the student we just added using the std::vector::back() function.

The following program should run:

Show Solution

1

myArray[0]=7;// put the value 7 in the first element of the array

classIntList

{

private:

    intm_list[10];

};

 

intmain()

{

    IntList list;

    // how do we access elements from m_list?

    return0;

}

classIntList

{

private:

    intm_list[10];

 

public:

    voidsetItem(intindex,intvalue){m_list[index]=value;}

    intgetItem(intindex){returnm_list[index];}

};

intmain()

{

    IntList list;

    list.setItem(2,3);

 

    return0;

}

classIntList

{

private:

    intm_list[10];

 

public:

    int*getList(){returnm_list;}

};

intmain()

{

    IntList list;

    list.getList()[2]=3;

 

    return0;

}

classIntList

{

private:

    intm_list[10];

 

public:

    int&operator[](constintindex);

};

 

int&IntList::operator[](constintindex)

{

    returnm_list[index];

}

    IntList list;

    list[2]=3;// set a value

    std::cout<<list[2];// get a value

 

    return0;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

classIntList

{

private:

    intm_list[10]={0,1,2,3,4,5,6,7,8,9};// give this class some initial state for this example

 

public:

    int&operator[](constintindex);

    constint&operator[](constintindex)const;

};

 

int&IntList::operator[](constintindex)// for non-const objects: can be used for assignment

{

    returnm_list[index];

}

 

constint&IntList::operator[](constintindex)const// for const objects: can only be used for access

{

    returnm_list[index];

}

 

intmain()

{

    IntList list;

    list[2]=3;// okay: calls non-const version of operator[]

    std::cout<<list[2];

 

    constIntList clist;

    clist[2]=3;// compile error: calls const version of operator[], which returns a const reference.  Cannot assign to this.

    std::cout<<clist[2];

 

    return0;

}

intlist[5];

list[7]=3;// index 7 is out of bounds!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#include <cassert> // for assert()

 

classIntList

{

private:

    intm_list[10];

 

public:

    int&operator[](constintindex);

};

 

int&IntList::operator[](constintindex)

{

    assert(index>=0&&index<10);

 

    returnm_list[index];

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

#include <cassert> // for assert()

 

classIntList

{

private:

    intm_list[10];

 

public:

    int&operator[](constintindex);

};

 

int&IntList::operator[](constintindex)

{

    assert(index>=0&&index<10);

 

    returnm_list[index];

}

 

intmain()

{

    IntList*list=newIntList;

    list[2]=3;// error: this will assume we're accessing index 2 of an array of IntLists

    delete list;

 

    return0;

}

intmain()

{

    IntList*list=newIntList;

    (*list)[2]=3;// get our IntList object, then call overloaded operator[]

    delete list;

 

    return0;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

#include <iostream>

#include <string>

 

classStupid

{

private:

 

public:

voidoperator[](std::stringindex);

};

 

// It doesn't make sense to overload operator[] to print something

// but it is the easiest way to show that the function parameter can be a non-integer

voidStupid::operator[](std::stringindex)

{

std::cout<<index;

}

 

intmain()

{

Stupid stupid;

stupid["Hello, world!"];

 

return0;

}

#include <string>

structStudentGrade

{

    std::stringname;

    chargrade;

};

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

#include <string>

#include <vector>

 

structStudentGrade

{

std::stringname;

chargrade;

};

 

classGradeMap

{

private:

std::vector<StudentGrade>m_map;

 

public:

GradeMap()

{

}

};

#include <iostream>

 

intmain()

{

GradeMap grades;

grades["Joe"]='A';

grades["Frank"]='B';

std::cout<<"Joe has a grade of "<<grades["Joe"]<<'\n';

std::cout<<"Frank has a grade of "<<grades["Frank"]<<'\n';

 

return0;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

One thought on “Overload Assignment Operator C++ Struct Member

Leave a comment

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