C++拷贝构造

目录

 

前言

概念介绍

拷贝构造常见问题

拷贝构造的实现与调用

总结

 


 

前言

        在 C++ 面向对象编程中,拷贝构造函数是构造函数的一种特殊形式,用于通过已有对象初始化新对象。同时,当类中包含指针成员时,还涉及深拷贝浅拷贝的区别。理解这些概念是掌握 C++ 对象生命周期和内存管理的关键。本文将详细介绍一下拷贝构造

概念介绍

拷贝构造函数

        拷贝构造函数是一种特殊的构造函数,其作用是用一个已存在的对象初始化另一个新对象。它的声明格式为:

类名(const 类名& 引用对象);

        需要注意的是,参数必须是引用类型(否则会引发 “拷贝构造的无限递归” 问题),且通常被声明为 const 以保证原对象不被修改。

拷贝构造的调用场景

C++ 中拷贝构造函数的调用主要有以下三种场景:
        1、用已有对象初始化新对象:例如 Person p2 = p1; 或 Person p3(p1);。
        2、值传递给函数参数:当函数参数以 “值传递” 形式接收对象时,会调用拷贝构造初始化形参。(这里是为什么拷贝构造的参数必须是引用的原因了,下面会具体介绍)
        3、以值形式返回局部对象:函数返回局部对象时,会调用拷贝构造生成一个匿名对象(现代编译器可能通过 RVO 优化省略此过程)。


浅拷贝与深拷贝

       浅拷贝:默认的拷贝行为,仅对成员变量进行 “值拷贝”。若类中包含指针成员,浅拷贝会导致多个对象共享同一块堆内存,析构时重复释放内存引发错误(重复释放同一块内存,导致程序崩溃)。
        深拷贝:手动实现的拷贝逻辑,对指针成员会重新申请堆内存并复制值,保证每个对象拥有独立的资源,避免内存问题。

深浅拷贝的选择

        当类中无指针 / 动态内存资源时,浅拷贝足够(如仅包含 int、double 等基本类型成员)。
        当类中有指针 / 动态内存资源时(如 new 申请的堆内存),必须实现深拷贝,确保每个对象的资源独立管理。

拷贝构造常见问题

问题 1:拷贝构造的无限递归

        场景:若拷贝构造函数的参数不是引用类型,而是值传递,会引发无限递归。

class Person {
public:
    // 错误的拷贝构造(参数为值传递)
    Person(Person other) { 
        cout << "拷贝构造" << endl;
    }
};

        问题原因:调用 Person(Person other) 时,实参传递给形参 other 会触发拷贝构造,而拷贝构造的调用又需要传递实参,从而陷入无限递归,最终导致栈溢出。

        解决方法:将拷贝构造的参数改为引用类型,且通常为 const 引用,如 Person(const Person& other)。

问题 2:浅拷贝导致的重复内存释放

        场景:类中包含指针成员,使用编译器默认的浅拷贝时,多个对象共享同一块堆内存,析构时重复释放引发程序崩溃。

class A {
    int* num;
public:
    A(int val) {
        num = new int(val); // 申请堆内存
    }
    // 未实现深拷贝(依赖编译器默认浅拷贝)
    ~A() {
        delete num; // 释放堆内存
    }
};

int main() {
    A a(3);
    A b = a; // 浅拷贝,a.num和b.num指向同一块堆内存
    return 0; // 析构时a和b会重复释放num指向的内存,程序崩溃
}

        问题原因:浅拷贝仅复制指针的值(地址),导致 a.num 和 b.num 指向同一块堆内存。析构时 a 先释放内存,b 析构时再次释放已释放的内存,引发 “双重释放” 错误。

        解决方法:手动实现深拷贝,在拷贝构造中为新对象重新申请堆内存并复制值,保证每个对象的指针指向独立的内存区域。

class A {
    int* num;
public:
    // 有参构造:为当前对象申请堆内存
    A(int val) {
        num = new int(val); // 申请堆内存并初始化
        cout << "有参构造:分配内存地址 " << num << endl;
    }

    // 深拷贝构造函数:为新对象独立申请堆内存
    A(const A& other) {
        // 复制原对象指针指向的值(而非地址)
        num = new int(*(other.num)); 
        cout << "深拷贝构造:新内存地址 " << num << "(复制自 " << other.num << ")" << endl;
    }

    // 析构函数:释放当前对象的堆内存
    ~A() {
        cout << "析构函数:释放内存地址 " << num << endl;
        delete num; // 安全释放当前对象独有的堆内存
    }
};

int main() {
    A a(3);       // 调用有参构造,分配堆内存
    A b = a;      // 调用深拷贝构造,为b分配独立堆内存(值与a相同)
    return 0;     // 析构时a和b释放各自的堆内存,无重复释放问题
}

拷贝构造的实现与调用

#include<iostream>
using namespace std;

class Person {
public:
    // 无参构造
    Person() {
        cout << "无参构造" << endl;
    }
    // 有参构造
    Person(int age) {
        cout << "有参构造" << endl;
    }
    // 拷贝构造(参数为const引用,避免递归调用)
    Person(const Person &other) {
        cout << "拷贝构造" << endl;
    }
    // 析构函数
    ~Person() {
        cout << "析构函数" << endl;
    }
};

// 场景1:值传递给函数参数
void fun(Person sum) {
    // 形参sum由实参通过拷贝构造初始化
}

// 场景2:以值形式返回局部对象
Person fun2() {
    Person p;
    return p; // 理论上调用拷贝构造,现代编译器可能优化(RVO)
}

int main() {
    // 场景1:用已有对象初始化新对象
    Person p1;
    Person p2 = p1;    // 隐式调用拷贝构造
    Person p3(p1);     // 显式调用拷贝构造

    // 场景1:函数值传递
    fun(p1);

    // 场景2:函数值返回
    fun2();

    // 匿名对象(仅当前行有效)
    Person();

    return 0;
}

 

 

总结

        拷贝构造函数是 C++ 中对象初始化的重要机制,需注意其调用场景和 “引用参数” 的设计,避免无限递归。
        浅拷贝适用于无动态资源的类,深拷贝是包含指针 / 堆内存类的必选,否则会引发内存泄漏或重复释放的严重问题。
        理解深浅拷贝的区别,是掌握 C++ 内存管理和对象生命周期的核心环节,在实际开发中需根据类的成员特性选择合适的拷贝策略,同时警惕拷贝构造的常见问题(如无限递归、重复释放内存)并采用对应解决方法。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值