当一个类没有显示定义一个拷贝构造函数,编译器会自动添加一个默认的拷贝构造函数。
#include <iostream>
using namespace std;
// Create a demo class
class A {
public:
int x;
};
int main() {
// Creating an a1 object
A a1;
a1.x = 10;
cout << "a1's x = " << a1.x << endl;
// Creating another object using a1
// Copy Constructor Calling
A a2(a1);
cout << "a2's x = " << a2.x;
return 0;
}默认生成的拷贝构造函数,会按照成员变量的声明顺序,依次进行赋值(member-wise initialization)。
也可以自己主动为类定义构造函数,格式如下。
className (const ClassName &obj) {
// Body of copy constructor
}const 关键字是可选的,因为构造函数里面不会去更改 obj,所以添加 const 修饰符。
Example:
#include <iostream>
using namespace std;
class A {
public:
int x;
A(){};
// Copy Constructor definition
A (A& obj) {
x = obj.x;
cout << "Copy constructor "
"called" << endl;
}
};
int main() {
// Creating an object of
// class A
A obj1;
obj1.x = 10;
cout << "obj1's x = " << obj1.x << endl;
// Creating another object by
// copying already created object
A obj2(obj1);
cout << "obj2's x = " << obj2.x;
return 0;
}既然,编译器会自动生成默认拷贝构造函数,为什么需要自定义拷贝构造函数? 默认生成的拷贝构造函数,仅进行了浅拷贝,如果一个类有一个指针或者动态分布的资源等,默认生成的构造函数仅拷贝了指针的地址,没有对指针指向的资源进行赋值,如果原始类对象析构后,资源也会被销毁,此时就会造成野指针问题,指针指向的内存资源被删除了。
下面是一个例子,需要显示声明拷贝构造函数。
#include <bits/stdc++.h>
using namespace std;
class String {
private:
char* s;
int size;
public:
String(const char* str){
size = strlen(str);
s = new char[size + 1];
strcpy(s, str);
}
~String() { delete[] s; }
// Copy constructor
String(const String& old_str){
size = old_str.size;
s = new char[size + 1];
strcpy(s, old_str.s);
}
void print() {
cout << s << endl;
}
void change(const char* str){
delete[] s;
size = strlen(str);
s = new char[size + 1];
strcpy(s, str);
}
};
int main() {
String str1("GeeksQuiz");
// Create str2 from str1
String str2 = str1;
str1.print();
str2.print();
// Update the str2 object
str2.change("GeeksforGeeks");
str1.print();
str2.print();
return 0;
}同拷贝构造函数一样,如果没有显示的定义赋值操作符,编译器也会自动生成赋值操作符,默认生成的赋值操作符和默认生成的拷贝构造函数存在同样的问题,都是对成员变量进行浅拷贝,当类成员指向动态分配资源时,原始对象析构时,资源会被销毁。此时需要自定义赋值操作符。
格式:
classname operator=(const classname& c) {
}因为赋值时并不会修改源对象,所以使用 const 修饰符。
下面是一个存在动态资源的类的赋值操作符实现方式,对其进行了深拷贝。
#include <bits/stdc++.h>
using namespace std;
// Dummy class
class GfG {
public:
int* arr;
int _size;
GfG(int size = 3) {
arr = new int[size];
_size = size;
}
// Overloaded assignment operator
GfG& operator=(const GfG& c) {
// self assignment check
if (this != &c) {
// Allocate new block
int* temp = arr;
arr = new(nothrow) int[c._size];
// If allocation fails
if (arr == nullptr) {
arr = temp;
}
else {
// Deallocate current block
delete [] temp;
// Copy data
_size = c._size;
memmove(arr, c.arr, _size * sizeof(int));
}
}
return *this;
}
// Deleting dynamic resources
~GfG() {
delete arr;
}
};
int main() {
GfG c1,c2;
// Initialize c2.arr with some values
for (int i = 0; i < c1._size; i++) {
c2.arr[i] = i + 10;
}
// Copying c2 object to c1
c1 = c2;
// Print the pointers to the allocated memory
cout << c1.arr << endl;
cout << c2.arr;
return 0;
}什么时候调用拷贝构造函数,什么时候调用赋值操作符呢?
可以看下面的例子:
MyClass t1, t2;
MyClass t3 = t1; // ----> (1)
t2 = t1; // -----> (2)因为(1)中创建了一个新的 t3 对象,所以调用拷贝构造函数。而(2)中 t2 是已经存在的对象,这里仅仅是将 t1 赋值给 t2,并不是一个初始化的过程,所以调用的是赋值运算符。
移动构造函数比较特殊,和拷贝构造函数目的相同,都是从一个已有的对象创建一个新的对象,不同的是它不是通过拷贝类成员实现的,而是使新对象指向内存中已经存在的对象,类似移动资源。
移动构造函数和移动赋值操作符接受的参数是一个右值引用对象。
ClassName(ClassName&& obj) {
data = obj.data;
// Nulling out the pointer to the temporary data
obj.data = nullptr;
}
ClassName& operator=(ClassName&& other) {
if (this != &other) {
data = other.data;
other.data = nullptr;
}
return *this;
}这里进行了一个资源转移的过程,将 obj.data 的资源转移到新对象中。
// C++ Program to illustrate how to use the move constructor
#include <iostream>
using namespace std;
class Simple {
private:
int* data;
public:
// Constructor
Simple(int value) : data(new int(value))
{
cout << "Constructor called, data = " << *data
<< endl;
}
// Destructor
~Simple()
{
delete data;
cout << "Destructor called" << endl;
}
// Move constructor
Simple(Simple&& other)
: data(other.data)
{
// nullify the other object resource
other.data = nullptr;
cout << "Move constructor called" << endl;
}
// Move assignment operator
Simple& operator=(Simple&& other)
{
if (this != &other) {
delete data; // Free existing resource
data = other.data; // Transfer ownership
other.data = nullptr; // Nullify source
cout << "Move assignment called" << endl;
}
return *this;
}
// Disable copy constructor and copy assignment operator
Simple(const Simple&) = delete;
Simple& operator=(const Simple&) = delete;
// Function to print the value
void print()
{
if (data) {
cout << "Data: " << *data << endl;
}
else {
cout << "Data is null" << endl;
}
}
};
int main()
{
// Create a Simple object with value 42
Simple obj1(42);
obj1.print();
// Move obj1 to obj2 using move constructor
Simple obj2 = move(obj1);
// Print obj2's data
obj2.print();
// Print obj1's data after move
obj1.print();
// Create another Simple object with value 84
Simple obj3(84);
// Move obj2 to obj3 using move assignment
obj3 = move(obj2);
obj3.print();
obj2.print();
return 0;
}std::move 方法将一个左值转换成右值引用,从而可以调用移动构造函数和移动赋值操作符。
移动构造函数因为避免了资源的拷贝,所以性能会更好。