1 JoJokora

Call Assignment Operator From Copy Constructor Definition

C++ gives almost god-like powers to the designer of a class. Object "life cycle" management means taking complete control over the behavior of objects during birth, reproduction, and death. You have already seen how constructors manage the birth of an object and how destructors are used to manage the death of an object. This section investigates the reproduction process: the use of copy constructors and assignment operators.

A copy constructor is a constructor that has a prototype like this:

ClassName(const ClassName & x);

The purpose of a copy constructor is to create an object that is an exact copy of an existing object of the same class.

An assignment operator for a class overloads the symbol and gives it a meaning that is specific to the class. There is one particular version of the assignment operator that has the following prototype:

ClassName& operator=(const ClassName& x);

Because it is possible to have several different overloaded versions of the in a class, we call this particular version the copy assignment operator.

Example 2.16. src/lifecycle/copyassign/fraction.h

[ . . . . ] class Fraction { public: Fraction(int n, int d) ; Fraction(const Fraction& other) ; Fraction& operator=(const Fraction& other) ; Fraction multiply(Fraction f2) ; static report() ; private: int m_Numer, m_Denom; static int s_assigns; static int s_copies; static int s_ctors; }; [ . . . . ]

Regular constructor

Copy constructor

Copy assignment operator


The version of in Example 2.16 has three counters, defined in Example 2.17, so that you can count the total number of times each member function is called. This should help you to better understand when objects are copied.

Example 2.17. src/lifecycle/copyassign/fraction.cpp

[ . . . . ] int Fraction::s_assigns = 0; int Fraction::s_copies = 0; int Fraction::s_ctors = 0; Fraction::Fraction(const Fraction& other) : m_Numer(other.m_Numer), m_Denom(other.m_Denom) { ++s_copies; } Fraction& Fraction::operator=(const Fraction& other) { if (this != &other) { m_Numer = other.m_Numer; m_Denom = other.m_Denom; ++s_assigns; } return *this; } [ . . . . ]

Static member definitions.

operator=() should always do nothing in the case of self- assignment.

operator=() should always return *this, to allow for chaining i.e. a=b=c.


Example 2.18 uses this class to create, copy, and assign some objects.

Example 2.18. src/lifecycle/copyassign/copyassign.cpp

#include <> #include "fraction.h" int main() { cout(stdout); Fraction twothirds(2,3); Fraction threequarters(3,4); Fraction acopy(twothirds); Fraction f4 = threequarters; cout << "after declarations - " << Fraction::report(); f4 = twothirds; cout << "\nbefore multiply - " << Fraction::report(); f4 = twothirds.multiply(threequarters); cout << "\nafter multiply - " << Fraction::report() << endl; return 0; }

Using 2-arg constructor.

Using copy constructor.

Also using copy constructor.

Assignment.

Several objects are created here.


Here is the output of this program.

copyassign> ./copyassign after declarations - [assigns: 0 copies: 2 ctors: 2] before multiply - [assigns: 1 copies: 2 ctors: 2] after multiply - [assigns: 2 copies: 3 ctors: 3] copyassign>
Question

As you can see, the call to creates three objects. Can you explain why?

2.11. Copy Constructors and Assignment Operators

Short answer - don't do it.

Details:

Notes:

  • When the copy constructor is called, it's constructing the new object with reference to the object being copied, but the default constructor does not run before the copy constructor. This means has an indeterminate value when the copy constructor starts running - you can assign to it, but to read from it is undefined behaviour, and to it considerably worse (if anything can be worse than UD! ;-)). So, just leave out that line.

Next, if tries to leverage the functionality from the copy constructor, it has to first release any existing data is pointing at or it will be leaked. Most people try to do that as follows (which is broken) - I think this is what you were trying for:

The problem with this is that if the creation of FeatureValue fails (e.g. because can't get the memory it wants), then the object is left with an invalid state (e.g. might be pointing off into space). Later when the destructor runs and does a , you have undefined behaviour (your program will probably crash).

You really should approach this more systematically... either writing it out step by step, or perhaps implementing a guaranteed non-throwing method (easy to do... just and , and using it ala:

That's easy and clean, but it has a couple minor performance/efficiency issues:

  • keeping any existing array around longer than necessary, increasing peak memory usage... you could call . In practice, most non-trivial programs wouldn't care about this unless the data structure in question was holding huge amounts of data (e.g. hundreds of megabytes or gigabytes for a PC app).

  • not even trying to reuse the existing memory - instead always doing another for (that can lead to reduced memory usage but isn't always worthwhile).

Ultimately, the reasons there can be distinct copy constructor and - rather than having the compiler automatically create one from the other - is that optimally efficient implementations can't - in general - leverage each other in the way you'd hoped.

Leave a Comment

(0 Comments)

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