拷贝是指将一个对象或者数据从一个地方复制到另一个地方,同时存在两个副本
拷贝一方面可以让代码按照预期运行,另一方不必要的拷贝会影响代码的运行效率。理解 C++中拷贝的工作原理对于编写高效的 C++代码是很有必要的

TIP💡

在读取或修改一个已经存在的对象时,应尽可能的避免拷贝,因为拷贝需要额外的时间开销

struct Vector
{
	float x,y;
}
 
int main()
{
	int a = 2;
	int b = a;	//b与a是独立的两个内存空间
	b = 3;      //a的值并不会受到影响
 
	Vector a = {2,3};
	Vector b = a;		//b与a仍然是独立的两个内存空间
	b.x = 5;			//a的值不受影响
 
	Vector* a = new Vector();
	Vector* b = a;		//b和a的"值"相同,即地址相同
	//case1
	b++;				//a的“值”不变,即指针指向地址不变
	//case2
	b->x = 2;			//因为a和b指向同一个地址,所以a和b同时变化
	
}

除了引用以外,每次使用 = 都是在进行一次拷贝,指针会复制内存地址值,变量会复制内存中的值

下面通过实现一个字符串类

#include<iostream>  
  
class String {  
private:  
    char* m_Buffer;  
    unsigned int m_Size;  
public:  
    String(const char* string) {  
        m_Size = strlen(string);  
        m_Buffer = new char[m_Size+1];    //为终止符留一个字节  
        memcpy(m_Buffer, string, m_Size);  
        m_Buffer[m_Size] = 0;  
    }  
    ~String()  
    {  
        delete[] m_Buffer;  
    }  
  
    char& operator[] (unsigned int index)  
    {  
        return m_Buffer[index];  
    }  
    friend std::ostream& operator<< (std::ostream& streamconst String& string);  
};  
  
std::ostream& operator<< (std::ostream& streamconst String& string) {  
    stream << string.m_Buffer;  
    return stream;  
}  
  
int main() {  
    String string = "ZhangSan";  
    String second = string;  
    second[2= 'e';    //两个字符串同时发生了变化  
    std::cout << string << std::endl;  
}

在程序结束时发生了崩溃,主要是字符串 second 在拷贝时,是将内部属性进行了拷贝,因此两者的 m_Buffer 指向的是同一片内存空间。在程序结束时,调用两者的析构函数对同一个内存空间调用了两次 delete,于是程序崩溃了

此外,修改 second 字符串导致 string 字符串的变化也同样说明了两者指向了同一个内存空间

因此这种类型的拷贝称之为浅拷贝

有时我们想使用独立于原对象的拷贝副本,那么需要进行深拷贝。深拷贝的方法有许多,例如 clone 函数或者自定义方法返回一个全新的对象。本文介绍一种使用拷贝构造函数的方法,当创建一个和源对象同类型的对象时会调用该方法,在 unique 中通过禁用该方法阻止对象拷贝

//c++默认拷贝构造函数(浅拷贝)
String(const String& other)  
    :m_Buffer(other.m_Buffer),m_Size(other.m_Size)  
{
 
}
//禁用对象拷贝,例如unique_ptr
String(const String& other) = delete;
//自定义拷贝构造函数(深拷贝)
String(const String& other)  
    :m_Size(other.m_Size)  
{  
    m_Buffer = new char[m_Size+1];  
    memcpy(m_Buffer, other.m_Buffer, m_Size+1);  
}
#include<iostream>  
  
class String {  
private:  
    char* m_Buffer;  
    unsigned int m_Size;  
public:  
    String(const char* string) {  
        m_Size = strlen(string);  
        m_Buffer = new char[m_Size+1]; 
        memcpy(m_Buffer, string, m_Size);  
        m_Buffer[m_Size] = 0;  
    }
    String(const String& other)
        :m_Size(other.m_Size)
    {          
        m_Buffer=newchar[m_Size+1];
        memcpy(m_Buffer,other.m_Buffer,m_Size+1);
    }
    ~String()  
    {  
        delete[] m_Buffer;  
    }  
  
    char& operator[] (unsigned int index)  
    {  
        return m_Buffer[index];  
    }  
    friend std::ostream& operator<< (std::ostream& streamconst String& string);  
};  
  
std::ostream& operator<< (std::ostream& streamconst String& string) {  
    stream << string.m_Buffer;  
    return stream;  
}
 
void printString(String string)  
{  
    std::cout << string << std::endl;  
}
  
int main() {  
    String string = "ZhangSan";  
	printString(string); 
}

在使用 printString 函数时会存在一次拷贝构造函数的调用, 每次拷贝对象都需要分配内存,复制内存以及释放内存,为了避免没必要的对象拷贝,只是单纯的使用原对象,建议使用引用传递对象

TIP💡

在多数情况下(除非复制会更快)参数对象建议使用引用传递,根据是否需要编辑对象决定 const 关键字的使用。在函数内部可以进一步决定传入对象是否需要拷贝

void printString(const String& string)  
{
	//可自行决定是否拷贝
	String copy = string;
    std::cout << string << std::endl;  
}