Overloading Operators C++ Assignment Answers

The assignment operator (operator=) is used to copy values from one object to another already existing object.

Assignment vs Copy constructor

The purpose of the copy constructor and the assignment operator are almost equivalent -- both copy one object to another. However, the copy constructor initializes new objects, whereas the assignment operator replaces the contents of existing objects.

The difference between the copy constructor and the assignment operator causes a lot of confusion for new programmers, but it’s really not all that difficult. Summarizing:

  • If a new object has to be created before the copying can occur, the copy constructor is used (note: this includes passing or returning objects by value).
  • If a new object does not have to be created before the copying can occur, the assignment operator is used.

Overloading the assignment operator

Overloading the assignment operator (operator=) is fairly straightforward, with one specific caveat that we’ll get to. The assignment operator must be overloaded as a member function.

This prints:

5/3

This should all be pretty straightforward by now. Our overloaded operator= returns *this, so that we can chain multiple assignments together:

Issues due to self-assignment

Here’s where things start to get a little more interesting. C++ allows self-assignment:

This will call f1.operator=(f1), and under the simplistic implementation above, all of the members will be assigned to themselves. In this particular example, the self-assignment causes each member to be assigned to itself, which has no overall impact, other than wasting time. In most cases, a self-assignment doesn’t need to do anything at all!

However, in cases where an assignment operator needs to dynamically assign memory, self-assignment can actually be dangerous:

First, run the program as it is. You’ll see that the program prints “Alex” as it should.

Now run the following program:

You’ll probably get garbage output (or a crash). What happened?

Consider what happens in the overloaded operator= when the implicit object AND the passed in parameter (str) are both variable alex. In this case, m_data is the same as str._m_data. The first thing that happens is that the function checks to see if the implicit object already has a string. If so, it needs to delete it, so we don’t end up with a memory leak. In this case, m_data is allocated, so the function deletes m_data. But str.m_data is pointing to the same address! This means that str.m_data is now a dangling pointer.

Later on, when we’re copying the data from str into our implicit object, we’re accessing dangling pointer str.m_data. That leaves us either copying garbage data or trying to access memory that our application no longer owns (crash).

Detecting and handling self-assignment

Fortunately, we can detect when self-assignment occurs. Here’s a better implementation of our overloaded operator= for the Fraction class:

By checking if our implicit object is the same as the one being passed in as a parameter, we can have our assignment operator just return immediately without doing any other work.

Note that there is no need to check for self-assignment in a copy-constructor. This is because the copy constructor is only called when new objects are being constructed, and there is no way to assign a newly created object to itself in a way that calls to copy constructor.

Default assignment operator

Unlike other operators, the compiler will provide a default public assignment operator for your class if you do not provide one. This assignment operator does memberwise assignment (which is essentially the same as the memberwise initialization that default copy constructors do).

Just like other constructors and operators, you can prevent assignments from being made by making your assignment operator private or using the delete keyword:

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

47

48

49

50

51

52

53

54

55

56

57

58

#include <cassert>

#include <iostream>

 

classFraction

{

private:

intm_numerator;

intm_denominator;

 

public:

    // Default constructor

    Fraction(intnumerator=0,intdenominator=1):

        m_numerator(numerator),m_denominator(denominator)

    {

        assert(denominator!=0);

    }

 

// Copy constructor

Fraction(constFraction&copy):

m_numerator(copy.m_numerator),m_denominator(copy.m_denominator)

{

// no need to check for a denominator of 0 here since copy must already be a valid Fraction

std::cout<<"Copy constructor called\n";// just to prove it works

}

 

        // Overloaded assignment

        Fraction&operator=(constFraction&fraction);

 

friendstd::ostream&operator<<(std::ostream&out,constFraction&f1);

        

};

 

std::ostream&operator<<(std::ostream&out,constFraction&f1)

{

out<<f1.m_numerator<<"/"<<f1.m_denominator;

returnout;

}

 

// A simplistic implementation of operator= (see better implementation below)

Fraction&Fraction::operator=(constFraction&fraction)

{

    // do the copy

    m_numerator=fraction.m_numerator;

    m_denominator=fraction.m_denominator;

 

    // return the existing object so we can chain this operator

    return*this;

}

 

intmain()

{

    Fraction fiveThirds(5,3);

    Fractionf;

    f=fiveThirds;// calls overloaded assignment

    std::cout<<f;

 

    return0;

}

intmain()

{

    Fraction f1(5,3);

    Fraction f2(7,2);

    Fraction f3(9,5);

 

    f1=f2=f3;// chained assignment

 

    return0;

}

intmain()

{

    Fraction f1(5,3);

    f1=f1;// self assignment

 

    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

47

48

49

50

51

52

53

54

55

56

57

58

59

60

#include <iostream>

 

classMyString

{

private:

    char*m_data;

    intm_length;

 

public:

    MyString(constchar*data="",intlength=0):

        m_length(length)

    {

        if(!length)

            m_data=nullptr;

        else

            m_data=newchar[length];

 

        for(inti=0;i<length;++i)

            m_data[i]=data[i];

    }

 

    // Overloaded assignment

    MyString&operator=(constMyString&str);

 

    friendstd::ostream&operator<<(std::ostream&out,constMyString&s);

};

 

std::ostream&operator<<(std::ostream&out,constMyString&s)

{

    out<<s.m_data;

    returnout;

}

 

// A simplistic implementation of operator= (do not use)

MyString&MyString::operator=(constMyString&str)

{

    // if data exists in the current string, delete it

    if(m_data)delete[]m_data;

 

    m_length=str.m_length;

 

    // copy the data from str to the implicit object

    m_data=newchar[str.m_length];

 

    for(inti=0;i<str.m_length;++i)

        m_data[i]=str.m_data[i];

 

    // return the existing object so we can chain this operator

    return*this;

}

 

intmain()

{

    MyString alex("Alex",5);// Meet Alex

    MyString employee;

    employee=alex;// Alex is our newest employee

    std::cout<<employee;// Say your name, employee

 

    return0;

}

intmain()

{

    MyString alex("Alex",5);// Meet Alex

    alex=alex;// Alex is himself

    std::cout<<alex;// Say your name, Alex

 

    return0;

}

// A better implementation of operator=

Fraction&Fraction::operator=(constFraction&fraction)

{

    // self-assignment guard

    if(this==&fraction)

        return*this;

 

    // do the copy

    m_numerator=fraction.m_numerator;

    m_denominator=fraction.m_denominator;

 

    // return the existing object so we can chain this operator

    return*this;

}

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

#include <cassert>

#include <iostream>

 

classFraction

{

private:

intm_numerator;

intm_denominator;

 

public:

    // Default constructor

    Fraction(intnumerator=0,intdenominator=1):

        m_numerator(numerator),m_denominator(denominator)

    {

        assert(denominator!=0);

    }

 

// Copy constructor

Fraction(constFraction&copy)=delete;

 

// Overloaded assignment

Fraction&operator=(constFraction&fraction)=delete;// no copies through assignment!

 

friendstd::ostream&operator<<(std::ostream&out,constFraction&f1);

        

};

 

std::ostream&operator<<(std::ostream&out,constFraction&f1)

{

out<<f1.m_numerator<<"/"<<f1.m_denominator;

returnout;

}

 

intmain()

{

    Fraction fiveThirds(5,3);

    Fractionf;

    f=fiveThirds;// compile error, operator= has been deleted

    std::cout<<f;

 

    return0;

}

If I overload operator '>', should I overload operator '<' for argumenst (const Message&, const Message&)?

Yes. In fact, it’s convention in most code to prefer the usage of over (don’t ask me why, probably historical). But more generally, always overload the complete set of related operators; in your case, this would probably also be , , and .

(Does the following code calls operator > for message1 object, or operator < message2 object?)

It always calls what it finds in the code. For the C++ compiler, there’s absolutely no connection between and . For us, they look similar but the compiler sees two completely different, unrelated symbols. So there’s no ambiguity: the compiler calls what it sees.

Does it need to be declared as member function?

No. In fact, it’s best not declared as a member function. Declaring it as a member function means that the first argument (i.e. the left-hand side of the expression) must really be a object, rather than an object that is implicitly convertible to a .

To understand this, consider the following case:

Now you can write the following comparison:

but you cannot write the following:

although there exists an implicit conversion from to (using the first constructor).

If, on the other hand, you had used a non-member function to implement the operator, both comparisons would work because C++ would know to call an implicit constructor on the first argument.

0 Replies to “Overloading Operators C++ Assignment Answers”