[C++] 拷贝构造函数和浅拷贝问题

本文深入探讨了拷贝构造函数与赋值运算符=在C++中引发的浅拷贝问题,特别是当成员变量为指针类型时,如何通过重载拷贝构造函数和赋值运算符来避免内存泄漏和双删问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.拷贝构造函数

当使用一个对象来初始化另一个同类对象时,编译器将自动生成一个构造函数,这个构造函数称为拷贝构造函数,其格式如下:

CustomClass(const CustomClass & obj);

在之前说过,创建类时,如果没有提供构造函数,那么编译器将会自动提供一个默认的构造函数。

拷贝构造函数也是一样,如果没有提供拷贝构造函数,那么编译器将会提供一个默认的拷贝构造函数。

1.1.调用时机

当程序中使用一个对象初始化一个新对象时,都会调用拷贝构造函数,这种情况包括以下两类:

  • 1.显式地使用旧对象初始化新对象。

这种情况非常明显,如:

MyString str1("Hello world.");
MyString str2 = str1;
MyString str3 = MyString(str1);
MyString str4(str1);
MyString *p_str = new MyString(str1);//p_str指向一个匿名MyString对象
  • 2.当函数按值传递对象或函数返回对象时.

因此,函数中经常使用引用传递,避免空间和时间的浪费。

2.由拷贝构造函数引发的浅拷贝问题

默认的拷贝构造函数逐个复制非静态成员,复制的是成员的值,称为浅拷贝。因此,如果成员为指针变量时,则容易引发问题,通过如下示例来说明。

这里我们定义一个Student类,如下:

student.h:

#include <iostream>

class Student
{
private:
        char *name;
        int age;
        static int number;
public:
        Student();
        Student(int age, const char * arr);
        ~Student();
        friend std::ostream & operator<<(std::ostream & os, const Student & stu);
};

student.cpp:

#include <cstring>
#include "student.h"

int Student::number = 0;
Student::Student() {
        age = 10;
        int len = 5;
        name = new char[len + 1];
        std::strcpy(name,"None");
        number ++;
        std::cout << name << " created by Stduent()" << std::endl;
}

Student::Student(int age, const char * arr) {
        this->age = age;
        int len = std::strlen(arr);
        name = new char[len + 1];
        std::strcpy(name,arr);
        number ++;
        std::cout << name << " created by Student(int,char *)" << std::endl;
}

Student::~Student() {

        number --;
        std::cout << name << " deleted by ~Student()" << std::endl;
        std::cout << number << " left." << std::endl;
        delete [] name;
}

std::ostream & operator<<(std::ostream & os, const Student & stu) {

        os << stu.name << std::endl;
        return os;
}

mainstu.cpp:

#include <iostream>
#include "student.h"

int main()
{
        Student stu1;
        Student stu2(12,"XiaoMing");
        Student stu3 = stu2;  //调用拷贝构造函数  
        return 0;
}

这里利用stu2对象来初始化stu3,在运行时,发生错误:

@ubuntu:$ g++ student.h student.cpp mainstu.cpp -o mainstu
@ubuntu:$ ./mainstu 
None created by Stduent()
XiaoMing created by Student(int,char *)
XiaoMing deleted by ~Student()
1 left.
 deleted by ~Student()
0 left.
*** Error in `./mainstu': double free or corruption (fasttop): 0x00000000010f9050 ***

为何会出现double free or corruption呢?

由于拷贝构造只是复制成员的值,因此该例中stu2.name和stu3.name就指向同一块内存,当程序结束调用后,析构stu2和stu3时,由于指向同一块内存,导致该区域内存被析构两次。

除以上这种情况外,如下的方法调用也会导致同样问题:

void showStu(const Student stu){
        std::cout << stu << std::endl;
}

由于函数按值传递时,也会调用拷贝构造函数,仅仅将实参的值拷贝给了形参,从而导致同一个内存被析构两次。

要解决由拷贝构造引起的浅拷贝问题,就必须显式实现拷贝构造函数,而不是使用由编译器提供的默认拷贝构造了。

Student::Student(const Student & stu) {
        if (this == &stu) return;
        this->age = stu.age;
        this->name = new char[std::strlen(stu.name) + 1]; 
        std::strcpy(this->name,stu.name);
        number ++;
}

在拷贝构造函数中,根据实参中数组的大小,重新动态申请了一块内存,从而避免两个对象共用同一个内存。

再次编译并运行程序,程序完整输出:

@ubuntu:$ g++ student.h student.cpp mainstu.cpp -o mainstu
@ubuntu:$ ./mainstu 
None created by Stduent()
XiaoMing created by Student(int,char *)
XiaoMing

XiaoMing deleted by ~Student()
2 left.
XiaoMing deleted by ~Student()
1 left.
None deleted by ~Student()
0 left.

3.赋值运算符=引起的浅拷贝问题

除了拷贝构造函数外,赋值运算符=也会引起浅拷贝问题,如:

int main()
{
	//...
    Student stu4;
	stu4 = stu3;//调用赋值运算符
	return 0;
}

默认情况下,复制运算符也是对成员的值进行复制,因此也将导致指针类型的成员变量指向同一个地址,从而引起错误。

所以,通过重载=运算符来解决这个问题,重载方式和拷贝构造函数类似,但有点差别:

  • 1.要避免自己给自己复制;
  • 2.由于目标对象可能存在以前分配的数据,故在动态申请内存前,需要先使用delete []释放以前的数据。

重载逻辑如下:

Student & Student::operator=(const Student & stu) {
	if (this == &stu)
		return *this;//避免释放掉自身内存
	delete[] name;
	this->age = stu.age;
	this->name = new char[std::strlen(stu.name) + 1];
	std::strcpy(this->name,stu.name);
	number++;
	return *this;
}

4.总结

凡是使用到new的类通常都需要提供拷贝构造函数和重载赋值运算符,以防止浅拷贝引起的问题。
如果希望这个类不使用拷贝构造和赋值运算符,可以将它们在类声明时定义为private形式,如:

class Student
{
private:
	Student(const Student & stu) {};
	Student & operator=(const Student & stu) { return *this;};
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值