Code Help - Copy Constructors

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Heap Data Member.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <string>

using namespace std;

class Critter
{
public:
	Critter(const string& name = "")
	{
		cout << "Constructor called\n";
		m_pName = new string(name);
	}

	~Critter()	//destructor
	{
		cout << "Destructor called\n";
		delete m_pName;
	}

	Critter(const Critter& c)	//copy constructor
	{
		cout << "Copy constructor called\n";
		m_pName = new string;
		*m_pName = c.GetName();
	}

	Critter& operator=(const Critter& c)	//overloaded assignment operator
	{
		cout << "Overloaded Assignment Operator called\n";

		if (this == &c)
		{
			return *this;
		}
		else
		{
			*m_pName = c.GetName();
			return *this;
		}
	}

	string GetName() const { return *m_pName; }
	void SetName(const string& name = "") { *m_pName = name; }
	void SayHi() const { cout << "Hi, my name is " << GetName() << "\n"; }

private:
	string* m_pName;
};

void testDestructor();
void testCopyConstructor(Critter copy);
void testAssignmentOp();

int _tmain(int argc, _TCHAR* argv[])
{
	testDestructor();
	cout << endl;

	Critter crit("Poochie");
	crit.SayHi();
	testCopyConstructor(crit);
	cout << endl;

	testAssignmentOp();

	system("Pause");
	return 0;
}

void testDestructor()
{
	Critter crit("Rover");
	crit.SayHi();
}

// passing object by value invokes its copy constructor
void testCopyConstructor(Critter copy)
{
	copy.SayHi();
}

void testAssignmentOp()
{
	Critter crit1("crit1");
	Critter crit2("crit2");
	crit1 = crit2;
	crit1.SayHi();
	crit2.SayHi();

	cout << "Setting name of crit1 back to 'crit1'\n";
	crit1.SetName("crit1");
	crit1.SayHi();
	crit2.SayHi();

	Critter crit("crit");
	crit = crit;
}


I have a brief understanding of how Copy Constructors work as I've read this here:
http://www.cplusplus.com/articles/y8hv0pDG/

However, there are extra things in here that confuse me which are not explained in the link above.

I need a quick rundown on how the copy constructors work and how Critter& operator=(const Critter& c) function work and how the this in teh code works.

Thanks.
Hi,

The purpose of a copy constructor is to initialise your new object by copying the content of a supplied, already existing object of the same type.

1
2
3
4
5
6
Critter(const Critter& c)	//copy constructor
{
	cout << "Copy constructor called\n";
	m_pName = new string;
	*m_pName = c.GetName();
}


So the above copy constructor is constructing a new Critter object, and to do that it's going to copy the contents of the provided Critter object 'c'.

The contents of the Critter object are just the string* m_pName; member, so within the copy constructor, we just have to copy the value of c's m_pName into our own m_pName.

c's m_pName value is retrieved by calling its GetName() function, and we can just assign the value returned from that to our own m_pName member.
You've done it in two steps, first to create the m_pName string, and second to set its value.

Jim
closed account (zb0S216C)
Copy-constructors are a special type of constructor which are implicitly defined by the compiler if the programmer does not explicitly declare one. First, consider this code:

1
2
3
4
class SimpleClass
{
   int Data_;
};

Nothing to it, right? Well, the compiler will implicitly declare and define a copy-constructor which will look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
class SimpleClass
{
  public:
    SimpleClass( SimpleClass const &Instance_ )
      : Data_( Instance_.Data_ )
    { }
};

int main( )
{
   SimpleClass SC_;
   // Do something with SC_...
}

Constructors and destructors implicitly declared by the compiler are non-virtual, in-lined and the constructors perform member-wise copying on each data-member. A constructor will only be defined if it's use is warranted. The parameter, "Instance_", of the copy-constructor is a reference to an instance of another "SimpleClass" instance -- the parameter is declared as constant so that temporary instances of "SimpleClass" can be bound to it.

The copy-assignment operator works the same way as the copy-constructor. Just like the constructors and destructor, the copy-assignment operator is also implicitly declared and defined by the compiler. Unlike the copy-constructor, however, the copy-assignment operator will return a reference to itself unless the programmer defines their own copy-assignment operator with a different return-type.

The "this" pointer is a parameter that's implicitly added to the parameter list of each function that's associated with a class. Static functions of a class don't count because they're not associated with a class. In non-constant member-functions, "this" is defined like so:

1
2
3
4
5
6
// [Note: "this" is defined by the compiler, so the following code is illegal -- end note]
class SimpleClass
{
  public:
    void MemberFunction( SimpleClass * const this, /* ...other params... */ );
};

As you can see, "this" is declared as a constant pointer to a non-constant "SimpleClass" instance. The reason why it's a constant pointer is because "this" cannot point to any other instance until the function call ends. Therefore, any attempt to redirect "this" will result in a compiler error.

In constant member-functions, the declaration of "this" is modified to respect the const qualifier, like so:

1
2
3
4
5
class SimpleClass
{
  public:
    void MemberFunction( SimpleClass const * const this, /* ...other params... */ ) const;
};

As you can see, the declaration of "this" is now a constant pointer to a constant "SimpleClass" instance. Consequently, any attempt to modify the instance pointed-to by "this" will result in an error unless the data-member you're trying to modify is "mutable". For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SimpleClass
{
  public:
    void ModifyA( ) const
    {
      DataA_ = 10; // Error: "this" is constant.
    }

    void ModifyB( ) const
    {
      DataB_ = 10; // OK
    }

  private:
    int DataA_;
    mutable int DataB_;
};

"mutable" data-members are allowed to change their state through constant member-functions -- this is the reason why "mutable" exists. If a data-member of a class instance must change, regardless of whether the instance is constant, a "mutable" data-member can change since "mutable" data-members are not subject to const-checking; hence the latter.

See here for more: http://www.cplusplus.com/doc/tutorial/classes/

Wazzak
Last edited on
The operator= function is similar, but slightly different in that it doesn't construct an object, it just assigns the members' values from another object of the same type.

Because of this, and because operator= is used as the right-hand-side (or do I mean left-hand-side?) of an operation, it needs to return a reference to itself. You can ignore the why for now, it's enough to just know that it has to return a reference to itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Critter& operator=(const Critter& c)	//overloaded assignment operator
	{
		cout << "Overloaded Assignment Operator called\n";

		if (this == &c)
		{
			return *this;
		}
		else
		{
			*m_pName = c.GetName();
			return *this;
		}
	}


So the above code is going to copy the content of the supplied Critter object 'c' to itself, in much the same way as the copy constructor did.
Line 11 performs this. Note that we don't have to create the string object like we did in the constructor. That's because this object has already been constructed and we're only assigning new value(s) to it - m_pName has already been created by either the constructor or the copy-constructor.

We do need a few extra bits of checking, though.

Where you see this within a class member's body, it refers to the current instance/object of that class. In our example above, this is a pointer to the Critter object being assigned to.

So we can use 'this' to return the reference to ourselves - by dereferencing the pointer, we get a reference. Line 12 does this and returns it from the function.

The only other difference is that we MUST NOT assign to ourselves within an operator= function. So the check this == &c is testing whether the supplied Critter 'c' is actually this, and it does this by comparing their addresses. If they're the same, just return without changing anything.

Jim
Thanks for the help so far guys. Appreciated.

How is Critter& operator=(const Critter& c) initialised to be used in the program? Also how does testCopyConstructor work?
It's not initialised, as such - operator= is just another member function of the Critter class, same as GetName. If you have an instance object of a Critter class, you can call any of its member functions. For example (untested code)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyClass
{
  MyClass(string name) : myName(name) { }

  string GetName() { return myName; }

  string SayHi() { return "Hi"; }

  string myName;

  MyClass& operator=(const MyClass& other) { ... }
}

void main()
{
  MyClass anInstanceOfMyClass ("Jim");

  cout << anInstanceOfMyClass.SayHi() << " ";
  cout << anInstanceOfMyClass.GetName();

  MyClass anotherInstance;
  anotherInstance = anInstanceOfMyClass; // operator= used here
}


GetName, SayHi and operator= are all member functions of MyClass.
We have an instance of MyClass, called anInstanceOfMyClass, and we can call any of the functions on that instance. The operator= is called slightly differently, as you can see. Actually, you can call it by saying something like anotherInstance.operator=(anInstanceOfMyClass), but ignore that.

Jim
The testCopyConstructor function takes a Critter object passed by value:

1
2
3
4
5
// passing object by value invokes its copy constructor
void testCopyConstructor(Critter copy)
{
	copy.SayHi();
}


As you can see, copy is a Critter object, not a Critter& reference to an object.
This means that when the function is called with ...

1
2
Critter crit("Poochie");
testCopyConstructor(crit);


... a copy of the crit instance of Critter is created and passed to the testCopyConstructor function where it is called 'copy'. The Critter copy-constructor is used to make that copied instance.

Jim
Thanks Jim. However I still have difficulty getting my head wrapped around on the operator=(const Critter& c). I don't fully understand the purpose of it quite yet.
Topic archived. No new replies allowed.