Char Array C++ Copy Constructor Assignment Operator

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;

}

Published by jsmith

Jan 27, 2010 (last update: Aug 20, 2010)

Copy constructors, assignment operators, and exception safe assignment

Score: 4.2/5 (2810 votes)

What is a copy constructor?

A copy constructor is a special constructor for a class/struct that is
used to make a copy of an existing instance. According to the C++
standard, the copy constructor for MyClass must have one of the
following signatures:



Note that none of the following constructors, despite the fact that
they could do the same thing as a copy constructor, are copy
constructors:



or my personal favorite way to create an infinite loop in C++:



When do I need to write a copy constructor?

First, you should understand that if you do not declare a copy
constructor, the compiler gives you one implicitly. The implicit
copy constructor does a member-wise copy of the source object.
For example, given the class:



the compiler-provided copy constructor is exactly equivalent to:



In many cases, this is sufficient. However, there are certain
circumstances where the member-wise copy version is not good enough.
By far, the most common reason the default copy constructor is not
sufficient is because the object contains raw pointers and you need
to take a "deep" copy of the pointer. That is, you don't want to
copy the pointer itself; rather you want to copy what the pointer
points to. Why do you need to take "deep" copies? This is
typically because the instance owns the pointer; that is, the
instance is responsible for calling delete on the pointer at some
point (probably the destructor). If two objects end up calling
delete on the same non-NULL pointer, heap corruption results.

Rarely you will come across a class that does not contain raw
pointers yet the default copy constructor is not sufficient.
An example of this is when you have a reference-counted object.
boost::shared_ptr<> is example.

Const correctness

When passing parameters by reference to functions or constructors, be very
careful about const correctness. Pass by non-const reference ONLY if
the function will modify the parameter and it is the intent to change
the caller's copy of the data, otherwise pass by const reference.

Why is this so important? There is a small clause in the C++ standard
that says that non-const references cannot bind to temporary objects.
A temporary object is an instance of an object that does not have a
variable name. For example:



is a temporary, because we have not given it a variable name. This
is not a temporary:



because the object's name is s.

What is the practical implication of all this? Consider the following:



Many of the STL containers and algorithms require that an object
be copyable. Typically, this means that you need to have the
copy constructor that takes a const reference, for the above
reasons.

What is an assignment operator?

The assignment operator for a class is what allows you to use
= to assign one instance to another. For example:



There are actually several different signatures that an
assignment operator can have:

(1) MyClass& operator=( const MyClass& rhs );
(2) MyClass& operator=( MyClass& rhs );
(3) MyClass& operator=( MyClass rhs );
(4) const MyClass& operator=( const MyClass& rhs );
(5) const MyClass& operator=( MyClass& rhs );
(6) const MyClass& operator=( MyClass rhs );
(7) MyClass operator=( const MyClass& rhs );
(8) MyClass operator=( MyClass& rhs );
(9) MyClass operator=( MyClass rhs );

These signatures permute both the return type and the parameter
type. While the return type may not be too important, choice
of the parameter type is critical.

(2), (5), and (8) pass the right-hand side by non-const reference,
and is not recommended. The problem with these signatures is that
the following code would not compile:



This is because the right-hand side of this assignment expression is
a temporary (un-named) object, and the C++ standard forbids the compiler
to pass a temporary object through a non-const reference parameter.

This leaves us with passing the right-hand side either by value or
by const reference. Although it would seem that passing by const
reference is more efficient than passing by value, we will see later
that for reasons of exception safety, making a temporary copy of the
source object is unavoidable, and therefore passing by value allows
us to write fewer lines of code.

When do I need to write an assignment operator?

First, you should understand that if you do not declare an
assignment operator, the compiler gives you one implicitly. The
implicit assignment operator does member-wise assignment of
each data member from the source object. For example, using
the class above, the compiler-provided assignment operator is
exactly equivalent to:



In general, any time you need to write your own custom copy
constructor, you also need to write a custom assignment operator.

What is meant by Exception Safe code?

A little interlude to talk about exception safety, because programmers
often misunderstand exception handling to be exception safety.

A function which modifies some "global" state (for example, a reference
parameter, or a member function that modifies the data members of its
instance) is said to be exception safe if it leaves the global state
well-defined in the event of an exception that is thrown at any point
during the function.

What does this really mean? Well, let's take a rather contrived
(and trite) example. This class wraps an array of some user-specified
type. It has two data members: a pointer to the array and a number of
elements in the array.



Now, assignment of one MyArray to another is easy, right?



Well, not so fast. The problem is, the line



could throw an exception. This line invokes operator= for type T, which
could be some user-defined type whose assignment operator might throw an
exception, perhaps an out-of-memory (std::bad_alloc) exception or some
other exception that the programmer of the user-defined type created.

What would happen if it did throw, say on copying the 3rd element of 10
total? Well, the stack is unwound until an appropriate handler is found.
Meanwhile, what is the state of our object? Well, we've reallocated our
array to hold 10 T's, but we've copied only 2 of them successfully. The
third one failed midway, and the remaining seven were never even attempted
to be copied. Furthermore, we haven't even changed numElements, so whatever
it held before, it still holds. Clearly this instance will lie about the
number of elements it contains if we call count() at this point.

But clearly it was never the intent of MyArray's programmer to have count()
give a wrong answer. Worse yet, there could be other member functions that
rely more heavily (even to the point of crashing) on numElements being correct.
Yikes -- this instance is clearly a timebomb waiting to go off.

This implementation of operator= is not exception safe: if an exception is
thrown during execution of the function, there is no telling what the state
of the object is; we can only assume that it is in such a bad state (ie,
it violates some of its own invariants) as to be unusable. If the object is
in a bad state, it might not even be possible to destroy the object without
crashing the program or causing MyArray to perhaps throw another exception.
And we know that the compiler runs destructors while unwinding the stack to
search for a handler. If an exception is thrown while unwinding the stack,
the program necessarily and unstoppably terminates.


How do I write an exception safe assignment operator?

The recommended way to write an exception safe assignment operator is via
the copy-swap idiom. What is the copy-swap idiom? Simply put, it is a two-
step algorithm: first make a copy, then swap with the copy. Here is our
exception safe version of operator=:



Here's where the difference between exception handling and exception safety
is important: we haven't prevented an exception from occurring; indeed,
the copy construction of tmp from rhs may throw since it will copy T's.
But, if the copy construction does throw, notice how the state of *this
has not changed, meaning that in the face of an exception, we can guarantee
that *this is still coherent, and furthermore, we can even say that it is
left unchanged.

But, you say, what about std::swap? Could it not throw? Yes and no. The
default std::swap<>, defined in <algorithm> can throw, since std::swap<>
looks like this:



The first line runs the copy constructor of T, which can throw; the
remaining lines are assignment operators which can also throw.

HOWEVER, if you have a type T for which the default std::swap() may result
in either T's copy constructor or assignment operator throwing, you are
politely required to provide a swap() overload for your type that does not
throw. [Since swap() cannot return failure, and you are not allowed to throw,
your swap() overload must always succeed.] By requiring that swap does not
throw, the above operator= is thus exception safe: either the object is
completely copied successfully, or the left-hand side is left unchanged.

Now you'll notice that our implementation of operator= makes a temporary
copy as its first line of code. Since we have to make a copy, we might as
well let the compiler do that for us automatically, so we can change the
signature of the function to take the right-hand side by value (ie, a copy)
rather than by reference, and this allows us to eliminate one line of code:


0 thoughts on “Char Array C++ Copy Constructor Assignment Operator

Leave a Reply

Your email address will not be published. Required fields are marked *