[C++ Learning] Classes and Objects | Copy Construction | Exploring Why Copy Constructors Need Reference Passing | Deep Copy | Getting to Know Operator Overloading

Written in front:

In the previous article, we started to learn the default member functions in the class,

Here is the portal, if you are interested, you can check it out: http://t.csdn.cn/iXdpH

In this article, we continue to learn about classes and objects.

Table of contents

Written in front:

1. Copy construction

2. Why does the copy constructor need to pass parameters by reference?

3. Deep copy

4. Getting to know operator overloading

Write at the end:


1. Copy construction

When we create an object, can we create an object that is exactly the same as an existing object?

In human terms, is it possible to copy an identical object?

At this time we need to use copy construction,

Copy construction is actually an overload of the constructor,

Look at the code:

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 2023, int month = 6, int day = 28) {
		_year = year;
		_month = month;
		_day = day;
	}

	//d2(d1) 拷贝构造
	Date(Date d) {
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);

	return 0;
}

If we want to implement the logic of copy construction, it is to pass the object we want to copy,

Then assign a value to our newly created object, but,

If we write like the code above, the compiler will report an error, why is this?

There is a rule in copy construction: the parameter of the copy constructor has one and only one, and it must be a reference to a class type object .

If you use the method of passing by value, the compiler will report an error directly, because it will cause infinite recursion.

That is, we need to implement it like this:

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 2023, int month = 6, int day = 28) {
		_year = year;
		_month = month;
		_day = day;
	}

	//d2(d1) 拷贝构造
	Date(Date& d) {
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);

	return 0;
}

2. Why does the copy constructor need to pass parameters by reference?

Then the question arises at this time, why an error will occur if you do not use references to pass parameters,

Or will there be so-called infinite recursion ?

Remember the function parameter passing we have learned, in fact, we need to copy a copy to the function as a formal parameter,

In C++, when a function passes parameters to pass a custom type, the copy of the custom type will automatically call the copy construction to complete.

This is stipulated by the ancestor of C++, the copy back of the custom type automatically calls the copy construction,

This will lead to, if the implemented copy construction is passing parameters by value, the custom type needs to be copied when passing parameters,

The custom type will automatically call the copy construction when copying, and a custom type needs to be passed when calling the copy construction.

Passing parameters of custom types needs to be copied, and the copy of custom types will automatically call the copy constructor......

This will cause the program to enter infinite recursion.

If you don't understand, you can read the above text several times,

In fact, there are only two core logics:

1. C++ stipulates that the copy construction will be called automatically when the custom type is copied

2. When a custom type is called as a function parameter by value, it needs to be copied once

Here a new question arises,

Why does C++ stipulate that the copy construction is automatically called when the custom type is copied?

In fact, C++ stipulates that:

1. For built-in types, it will be copied directly

2. For custom types, its copy construction will be called

As for why it is necessary to call his copy structure, we will learn the knowledge of deep and shallow copy later, and we will understand by then.

However, the implementation of copy construction is generally used to add a const in front:

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 2023, int month = 6, int day = 28) {
		_year = year;
		_month = month;
		_day = day;
	}

	//d2(d1) 拷贝构造
	Date(const Date& d) {
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);

	return 0;
}

There is a layer of const to protect this object, and the code is more robust.

Here is an example by the way,

Take a look at this code:

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 2023, int month = 6, int day = 28) {
		_year = year;
		_month = month;
		_day = day;
	}

	//d2(d1) 拷贝构造
	Date(Date& d) {
		d._year = _year;
		d._month = _month;
		d._day = _day;

		//_year = d._year;
		//_month = d._month;
		//_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);

	return 0;
}

The logic of this code is actually reversed, but the compiler does not report an error.

If you can't find it, it will still cause a lot of problems and troubles:

 

However, if we add const to modify this object:

The compiler will report an error reminder, so that the code is not easy to make mistakes. 

So our final full body code looks like this:

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 2023, int month = 6, int day = 28) {
		_year = year;
		_month = month;
		_day = day;
	}

	//d2(d1) 拷贝构造
	Date(const Date& d) {
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);

	return 0;
}

3. Deep copy

Before learning about deep copy, let's take a look at what the compiler does for our default generated copy construction:

Take a look at this code:

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 2023, int month = 6, int day = 28) {
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);

	return 0;
}

We didn't implement copy construction ourselves,

By debugging, you can see what the default generated copy construction does:

At this point we can see that d1 has been initialized through the constructor,

 

 We can see that the copy construction generated by default completes the copy,

Moreover, we can also find that the copy structure generated by default seems to be the same as the function we implemented,

That is to say, in the future, if we want to copy the structure of the Date class, we don't need to implement it ourselves.

Just use the one generated by default.

Then if this is the case, we still need to learn what to do with the copy structure, and let the compiler automatically do it for us.

That's definitely not such a good thing, let's look at this example:

class Stack {
public:
	Stack(int capacity = 4) {
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr) {
			perror("Stack::malloc::fail");
			return;
		}
		
		_capacity = capacity;
		_top = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st1;
	Stack st2(st1);

	return 0;
}

Let's debug to see what the compiler's default copy construction does:

At first glance, this seems to be okay.

The copy structure generated by default also helps us make a copy of exactly the same data.

Don't worry, we haven't implemented a destructor for this stack yet, otherwise there will be a risk of memory leaks:

#include <iostream>
using namespace std;

class Stack {
public:
	Stack(int capacity = 4) {
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr) {
			perror("Stack::malloc::fail");
			return;
		}
		
		_capacity = capacity;
		_top = 0;
	}

	~Stack() {
		free(_a);
		_a = nullptr;
		_capacity = 0;
		_top = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st1;
	Stack st2(st1);

	return 0;
}

Again this code, but we added the destructor,

At this time, we first call the first destructor to release the space of st2:

 When we go to call the destructor of st1 again, just go to free, click it, very quickly

 The program crashes, why is this,

Think about it carefully, which pointer was freed just now, if the _a pointers of the two stack objects point to the same area,

So what happens? The destructor frees both stack spaces once free,

However, two stack objects need to be destructed twice, and the same space is freed twice, and the program crashes naturally.

Have you found out that the default copy constructor automatically generated by the compiler cannot be used at this time.

So how to solve it?

In fact, it is solved by the so-called deep copy:

#include <iostream>
#include <cstring>
using namespace std;

class Stack {
public:
	Stack(int capacity = 4) {
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr) {
			perror("Stack::malloc::fail");
			return;
		}
		
		_capacity = capacity;
		_top = 0;
	}

	//深拷贝
	Stack(const Stack& st) {
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (_a == nullptr) {
			perror("Stack::malloc::fail");
			return;
		}

		memcpy(_a, st._a, sizeof(int) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}

	~Stack() {
		free(_a);
		_a = nullptr;
		_capacity = 0;
		_top = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st1;
	Stack st2(st1);

	return 0;
}

At this time, we can see that after implementing deep copy,

The address of the space pointed to by the two _a is different, which means that they are two unrelated spaces.

Of course, this subroutine will not crash. 

So if you encounter such a situation where you need to open up space, you have to manually implement deep copying yourself.

4. Getting to know operator overloading

Let's look at such an example:

#include <iostream>
using namespace std;

class A {
public:
	A(int x = 10, int y = 10) {
		_x = x;
		_y = y;
	}

private:
	int _x;
	int _y;
};

int main()
{
	A a1(10, 10);
	A a2(20, 20);

	return 0;
}

Suppose we want to compare a1 and a2 to see who is bigger,

Can a1 > a2 be used directly for comparison? obviously not,

The compiler does not know the comparison rules of your custom type,

If we want to compare a custom type, it is best to write a comparison function:

#include <iostream>
using namespace std;

class A {
public:
	A(int x = 10, int y = 10) {
		_x = x;
		_y = y;
	}

//private:
	int _x;
	int _y;
};

//这段比较逻辑是:如果a1 > a2 就返回true,否则就返回false
bool Compare(A a1, A a2) {
	if (a1._x > a2._x) {
		return true;
	}
	else if (a1._x == a2._x) {
		if (a1._y > a2._y) {
			return true;
		}
		else if (a1._y == a2._y) {
			return false;
		}
		else {
			return false;
		}
	}
	else {
		return false;
	}
}

int main()
{
	A a1(10, 10);
	A a2(20, 20);
	cout << Compare(a1, a2) << endl;

	return 0;
}

If this is the case, we not only need to release the private member variables,

It is very uncomfortable to have to call the function to compare.

C++ sets up operator overloading:

#include <iostream>
using namespace std;

class A {
public:
	A(int x = 10, int y = 10) {
		_x = x;
		_y = y;
	}

//private:
	int _x;
	int _y;
};

//这段比较逻辑是:如果a1 > a2 就返回true,否则就返回false
bool operator>(A a1, A a2) {
	if (a1._x > a2._x) {
		return true;
	}
	else if (a1._x == a2._x) {
		if (a1._y > a2._y) {
			return true;
		}
		else if (a1._y == a2._y) {
			return false;
		}
		else {
			return false;
		}
	}
	else {
		return false;
	}
}

int main()
{
	A a1(10, 10);
	A a2(20, 20);

	//这样就能直接使用 > 来进行比较,而使用 > 其实就是使用了下面那个函数
	cout << (a1 > a2) << endl; 
	cout << operator>(a1, a2) << endl;

	return 0;
}

In this way, we have successfully achieved direct comparison with > through the operator overloading supported by C++.

But what about the member variables released from private?

We can put the operator function into the class,

How should I do it? We will reveal it in the next article.

Write at the end:

The above is the content of this article, thank you for reading.

If you feel that you have gained something, you can give the blogger a like .

If there are omissions or mistakes in the content of the article, please private message the blogger or point it out in the comment area~

Guess you like

Origin blog.csdn.net/Locky136/article/details/131429998