0% found this document useful (0 votes)
9 views33 pages

1 - Copy Constructors

Uploaded by

i.a.m
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views33 pages

1 - Copy Constructors

Uploaded by

i.a.m
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 33

Object-Oriented

Programming
Mohammad Salman
Dean’s Fellow
(SS) Some insights from the lab
• An object gets implicitly passed to the function that is called
through that object.

TollBooth t1;
t1.show();// compiler sees this as, TollBooth::show(&t1)
(SS) Some insights from the lab
• Within TollBooth::show(), you can access t1’s attributes
just by writing out the names of the attributes.

void TollBooth::show() const


{
std::cout << cars << ‘ ‘ << money << ‘\n’;
}
These are referring to t1’s
attributes (or whatever the calling
object will be).
(SS) Some insights from the lab
• Similarly, observe the following.

Complex c1{1, 9};


Complex c2{-4, 10.2};

c1.add(c2); // compiler reads, Complex::add(&c1, c2)


(SS) Some insights from the lab c1.add(c2);

• Within Complex::add(), this is how you can access attributes


of c1 and c2.

Complex Complex::add(const Complex& c2)


{
c2.real; c2.imag; // c2’s attributes
real; imag; // c1’s attributes
}
(SS) Some insights from the lab
• With this example, also note the following. c1.add(c2);
c1 c2
real imag real imag
add() add()
subtract() subtract()
multiply() multiply()
… …

Accessing c2’s attributes in c1’s function


Keep in mind that real and
call works because access specifiers
imag are private members
work on a per-class basis (c1 and c2 of class Complex.
belong to the same class, so this works).
Copy Constructors
Creating new objects using existing ones

https://www.learncpp.com/cpp-tutorial/introduction-to-the-copy-constructor/
In the first line, f has been initialized using the provided parameters.
In the second line, a new object is created (fCopy) using an existing object of the same class (f).

Our ctor’s parameters are not suited for this.


Yet, this program will work just fine!

class Fraction int main()


{ {
private: // Calls Fraction(int, int) constructor
int m_num; Fraction f{ 5, 3 };
int m_den;
// What constructor is used here?
public: Fraction fCopy{ f };
Fraction(int num, int den)
: m_num{ num }, m_den{ den } f.print();
{ fCopy.print();
}
return 0;
void print() const }
{
std::cout << "Fraction(" << m_num << ", "
<< m_den << ")\n";
}
};
What is a Copy Constructor?
• Copy constructor is a constructor that is used to initialize an
object with an existing object of the same type.

• The newly created object will be a copy of the object passed


in as the initializer.

• If you do not provide a copy constructor for your classes, the


compiler will provide a public implicit copy constructor for
you.
Fraction fCopy{ f };

• fCopy’s attributes will be initialized using the values of the attributes of f.

• Note that the data of both objects is still independent from one another.
• Changing f’s attributes will not impact fCopy’s attributes and vice versa.

Fraction f Fraction fCopy

m_num = 5 m_den = 3 m_num = 5 m_den = 3


void print() const void print() const
This is what a copy ctor looks like.
Take note of the parameter that the copy ctor is accepting.

class Fraction int main()


{ {
private: // Calls the regular constructor
int m_num; Fraction f{ 5, 3 };
int m_den;
// Calls copy constructor
public: Fraction fCopy{ f };
Fraction(int num, int den)
: m_num{ num }, m_den{ den } return 0;
{ }
}
// Copy constructor
Fraction(const Fraction& frac)
: m_num{ frac.m_num }, m_den{ frac.m_den }
{
// just to prove it works
std::cout << "Copy constructor called\n"; This is an application of
} “Access controls work
}; on a per-class basis”.
Where else would a copy constructor be
used?
• Another situation where copy ctors would be called will be when
you pass objects by value to some function.

• Pass by value means that copies of the passed parameters are


created.

• If your parameter is an object, then its copy will be created.


• And for its copy to be created, the copy ctor will be called.
Question // Calls the regular constructor
Fraction f { 5, 3 };
Do you think that you could pass the object by value in the copy ctor?
// Calls copy constructor
Fraction fCopy { f };
// Pass by value
Fraction(Fraction frac)
: m_num{ frac.m_num }, m_den{ frac.m_den } Not OK
{ Compiler error
}

// Pass by value; the copy is const


Fraction(const Fraction frac)
: m_num{ frac.m_num }, m_den{ frac.m_den } Not OK
{ Compiler error
}

// Pass by reference
Fraction(Fraction& frac)
: m_num{ frac.m_num }, m_den{ frac.m_den } OK (but const is better)
{
}
https://www.learncpp.com/cpp-tutorial/introduction-to-the-copy-constructor/
Why would you ever need
to write your own copy
constructor though?
Similar reason as when you would need to write your own
destructor…
Precursor to the RULE OF THREE
At the moment, the implicit copy ctor will be used.
Attributes of list1 will be copied over to attributes of list2.
What will this result in?
class MyList int main()
{ {
private: MyList list1{ 5 };
double* m_array;
int m_length; {
MyList list2{ list1 };
public: }
int getLength() const { return m_length; }
return 0;
MyList(int length) }
: m_array{ new double[length]{} },
m_length{ length }
{}
Btw, my goal here is to create list2
~MyList() with its own array of doubles, but is
{ a copy of the array of list1.
delete[] m_array;
}
};
At the moment, the implicit copy ctor will be used.
Attributes of list1 will be copied over to attributes of list2.
What will this result in?

Stack Memory Heap Memory


Automatic Memory Allocation Dynamic Memory Allocation

double* = 0x90 int = 5 0.0 0.0 0.0 0.0 0.0


list1
Methods
0x20
0x90

double* = 0x90 int = 5


list2 Methods
This is known as a shallow copy.
0x50
What will happen to the dynamic array when list2 goes out of scope?
That array will be deallocated because of the call to the dtor for list2.
list1’s pointer attribute will be left dangling.
class MyList int main()
{ {
private: MyList list1{ 5 };
double* m_array;
int m_length; {
MyList list2{ list1 };
public: }
int getLength() const { return m_length; }
return 0;
MyList(int length) }
: m_array{ new double[length]{} },
m_length{ length }
{}

~MyList()
{
delete[] m_array;
}
};
As list2 is about to go out of scope, the compiler triggers a call to the dtor for list2. Exit
The array on the heap gets deallocated in the dtor call for list2. animations
But list1’s pointer attribute is left dangling. are enabled
on this slide

Stack Memory Heap Memory


Automatic Memory Allocation Dynamic Memory Allocation

double* = 0x90 int = 5 0.0 0.0 0.0 0.0 0.0


list1
Methods
0x20
0x90

double* = 0x90 int = 5


list2 Methods
0x50
This is a good situation to write your own copy ctor.
Here, you allocate separate memory on the heap for the copy object.
Recall that in these type of situations, you should write your own dtor and your own copy ctor.
There is a third thing that you should also write,
which we’ll see later in operator overloading. // Within the class
RULE OF THREE MyList(const MyList& ob)
class MyList
: m_array{ new
{ double[ob.m_length] },
private: m_length{ ob.m_length }
double* m_array;
int m_length; {
for(int i = 0; i < ob.m_length; ++i)
public:
int getLength() const { return m_length; } {
m_array[i] = ob.m_array[i];
MyList(int length)
: m_array{ new double[length]{} },
}
m_length{ length } }
{} I am copying the elements in the array
associated with list1 to the array associated
~MyList()
{
with list2 within the copy ctor’s body.
delete[] m_array;
} This is known as a deep copy.
};
(SS) Something to keep in mind…
• We mentioned that if we don’t write any ctor (regular and copy), the
compiler provides us with a default regular ctor and an implicit copy
ctor.

• If we write a regular ctor, but do not write a copy ctor, then the
compiler doesn’t provide a regular default ctor, but it will provide an
implicit copy ctor.

• However, if you don’t write a regular ctor, but you do write a copy ctor,
then the compiler will not provide you with an implicit copy ctor, but it
will also not provide you with a regular default ctor.
(SS) Something to keep in mind…

Have I written a regular ctor Have I written What will the compiler provide
(default or otherwise)? a copy ctor? me with?
No No Regular default and copy ctors
Yes No Implicit copy ctor
Yes Yes Nothing
No Yes Nothing

Note that this table is just focusing on these


two types of constructors.

We are not talking about other things that the


compiler provides us with.
Some Questions…
Let’s go back to Fractions and mess around with access levels
of constructors…
What if I set my copy ctor to private?
Error! Since you wrote your own copy ctor, the compiler doesn’t provide you with one.
Your own copy ctor is private. So how can it be called from main().
class Fraction int main()
{ {
private: // Calls the regular constructor
int m_num; Fraction f { 5, 3 };
int m_den;
// Calls copy constructor
// Copy constructor Fraction fCopy { f };
Fraction(const Fraction& frac)
: m_num{ frac.m_num }, return 0;
m_den{ frac.m_den } }
{
}

public:
Fraction(int num, int den)
: m_num{num}, m_den{den}
{
}
};
What if I set my regular ctor to private?
Error! Since you wrote your own ctor, the compiler doesn’t provide you with one.
Your own ctor is private. So how can it be called from main().
class Fraction int main()
{ {
private: // Calls the regular constructor
int m_num; Fraction f { 5, 3 };
int m_den;
// Calls copy constructor
// Copy constructor Fraction fCopy { f };
Fraction(const Fraction& frac)
: m_num{ frac.m_num }, return 0;
m_den{ frac.m_den } }
{
}

Fraction(int num, int den)


: m_num{num}, m_den{den}
{
}

public:

};
Will this array creation work?
Error! This array is meant to initialize an array of Fractions. That means each element will be a Fraction-type
object.
Your ctor, works with two parameters. So each object in the array should be initialized with two parameters.
You are not doing that…
int main()
{
Fraction f{1000, 333};
class Fraction f.show();
{
private: Fraction fArr[3];
int m_num; for(int i{}; i<3; i++)
int m_den; {
fArr[i].show();
public: }
Fraction(int num, int den)
: m_num{num}, return 0;
m_den{den} }
{}

void show() const


{
std::cout << m_num << '/' << m_den << '\n';
}
};
Will this array creation work?
Error! This array is meant to initialize an array of Fractions. That means each element will be a Fraction-type
object.
Your ctor, works with two parameters. So each object in the array should be initialized with two parameters.
You are not doing that…
So now you’re initializing each Fraction object so that the ctor could be properly called for each of the Fraction
being created.
You can actually add a print statement within the ctor to see.
Another option could be to create a default ctor (zero parameters) so that arrays could be created w/o init. values.
int main()
{
Fraction f{1000, 333};
class Fraction f.show();
{
private: Fraction fArr[3]{ {1, 1}, {1, 2}, {1, 4} };
int m_num; for(int i{}; i<3; i++)
int m_den; {
fArr[i].show();
public: }
Fraction(int num, int den)
: m_num{num}, return 0;
m_den{den} }
{ std::cout << "My ctor\n"; }

void show() const


{
std::cout << m_num << '/' << m_den << '\n';
}
};
Self Study. In case of errors, fix them. Also, determine the output of this program. Try not using an editor.

class Fraction int main()


{ {
private: Fraction* fArr{ new Fraction[3] };
int m_num;
int m_den;
for(int i{}; i<3; i++)
public: fArr[i].show();
Fraction(int num, int den)
: m_num{num}, delete[] fArr;
m_den{den}
{ std::cout << "My ctor\n"; } return 0;
~Fraction() { std::cout << "My dtor\n"; }
}

void show() const


{
std::cout << m_num << '/' << m_den << '\n';
}
};
Miscellaneous Concepts
Self Study
Concept: Data type of string literals is const char[] (or const char*).

#include <iostream>

void printString1(const char str[]); For test case 1, the const is not required in
printString1(). For test case 2, it is required.
int main()
{
// Test case 1
char arr[]{ "hello" };
Extension
printString1(arr); Keep main() the same, and change the data type of
str in printString1() to std::string.
// Test case 2 • Try passing by value.
printString1("world"); • Try passing by reference.
• Try passing by const reference.
return 0;
}

void printString1(const char str[])


{
std::cout << "In printString1(): ";
std::cout << str << '\n';
}
Extension to the idea of const

A const method ensures that the implicit object (the object through which the method is being called) is considered const within
the function. Consider two situations:
• It is valid to have a non-const object call a const method.
• In this case, this object is assumed const within the method, but not in the scope where it was initialized.

• It is valid to have a const object call a const method.


• In this case, this object was const anyways, and will be considered const within the method as well. The compiler is fine
with this, since the const-ness of the already const object is guaranteed within this method.

As such, const methods can be called by both const and non-const objects.

However, let’s say you have a method that is non-const. A non-const method allows the implicit object to be modified,
potentially.
• It is valid to have a non-const object call a non-const method.
• In this case, if the programmer wants, they can make modifications to the implicit object within this method.

• It is invalid, however, to have a const object call a non-const method.


• A non-const method allows an object to be modified. However, if an object was initialized as a const object to begin
with, it cannot be used to call a method that could allow itself to potentially change.
• Since there are no guarantees that the const object would be unchanged within this method, the compiler does not allow
this to happen.

As such, non-const methods can be called by non-const objects, but not const objects.
Extension to the idea of const
As such, const methods can be called by both const and non-const objects.
As such, non-const methods can be called by non-const objects, but not const objects.

class Fraction
{ int main()
public: {
Fraction(int num, int den) const Fraction PI{ 22, 7 };
: m_num{ num }, m_den{ den } Fraction f1{ 2, 5 };
{
if (m_den == 0) // const methods can be called by both const
m_den = 1; and non-const objects
} f1.print();
PI.print();
void print() const // const method
{ // non-const methods cannot be called by const
std::cout << m_num << '/' << m_den << '\n'; objects.
} f1.update(2, 7);
PI.update(100, 3); // compile-time error
void update(int num, int den) // non-const method
{ return 0;
m_num = num; }
m_den = den;
}

private:
int m_num;
int m_den;
};

You might also like