C++拷贝构造函数详解:从原理到深拷贝实战

周叙
周叙
发布时间:2026-10-15| 5人浏览过
原创

C++类对象的复制依赖拷贝构造函数。本文将解析其三种典型触发场景,对比浅拷贝与深拷贝的内存差异,并通过代码示例演示如何避免指针成员带来的内存错误。

拷贝构造函数的作用与触发机制

在C++中,同类型的类对象可以通过拷贝构造函数完成整个复制过程。与普通类型简单的赋值不同,类对象内部可能包含多个成员变量,甚至动态分配的内存资源。拷贝构造函数本质上也是构造函数的一种重载形式,其核心目的是使用一个已有的对象来初始化新对象。

以下情况会自动调用拷贝构造函数:

  • 对象初始化:使用同一类的另一个对象直接初始化新对象,如 CExample B = A;。
  • 值传递参数:函数参数为类类型的值时,实参会被复制到形参。
  • 值类型返回值:函数返回值为局部对象时,会生成临时对象并将其返回给调用者。
class A
{
public:
    A(int i = 0):m_i(i)
    {
        cout<<"A(int) "<<m_i<<endl;
    }
    // 拷贝构造函数
    A(const A &a):m_i(a.m_i)
    {
        cout<<"A(A) "<<m_i<<endl;
    }
    ~A()
    {
        cout<<"~A "<<m_i<<endl;
    }
private:
    int m_i;
};

void fn(A t) // 2. 值传递,用实参c构造形参t,调用拷贝构造
{
    cout<<"fn end"<<endl;
}

A test()
{
    A d(40); // 普通构造
    return d; // 3. 返回局部对象d,构造临时对象,随后d析构
}

int main()
{
    A a(20);       // 1. 普通构造
    A b = a;       // 2. 用a初始化b,调用拷贝构造
    
    A c(30);
    fn(c);         // 3. 值传递,调用拷贝构造
    
    c = test();    // 4. test返回临时对象,赋值给c
    
    A t = test();  // 5. 临时对象初始化t
    
    return 0;
}

未定义拷贝构造的风险:位拷贝

如果程序员未显式定义拷贝构造函数,编译器会生成默认的拷贝构造函数。默认的拷贝行为是“位拷贝”(Bitwise Copy),即按内存字节顺序简单复制数据成员的值。

对于仅包含基本数据类型(如 int, double)的类,位拷贝通常没有问题。然而,当类中包含指针成员或动态分配的资源时,位拷贝会导致两个对象的指针指向同一块堆内存。当对象生命周期结束时,析构函数会多次释放同一块内存,引发运行时错误或内存泄漏。

深拷贝与浅拷贝的核心差异

浅拷贝的问题演示

浅拷贝是指仅复制对象的基本成员和指针变量的值,而不复制指针指向的内容。这会导致多个对象共享同一块动态内存。

考虑一个简单的字符串类 Str,其内部维护一个动态分配的字符数组 m_str:

class Str
{
public:
    Str(const char *str = "")
    {
        m_str = new char[strlen(str)+1];
        strcpy(m_str,str);
    }
    ~Str()
    {
        delete[] m_str; // 释放动态内存
    }
    void Print()
    {
        cout<<m_str<<endl;
    }
private:
    char *m_str; // 指针成员
};

void testShallowCopy()
{
    Str s1("Hello");
    Str s2(s1);     // 调用默认拷贝构造(浅拷贝)
                    // s2.m_str 指向与 s1.m_str 相同的内存地址
    
    s1.Print();
    s2.Print();
    // 函数结束时,s2析构释放内存,s1再次析构时发生重复释放错误
}

在上述场景中,s1 和 s2 的成员指针 m_str 拥有相同的地址。析构顺序若先释放 s2 的空间,s1 在随后的析构中访问已释放的内存,导致程序崩溃。

深拷贝的实现方案

深拷贝要求为每个对象分配独立的内存空间,并复制源对象指明的内容。这需要手动定义拷贝构造函数,在新对象中重新 new 一块内存,并使用 strcpy 等函数复制具体内容。

以下是实现深拷贝的正确写法:

class StrDeep
{
public:
    StrDeep(const char *str = "")
    {
        m_str = new char[strlen(str)+1];
        strcpy(m_str,str);
    }
    
    // 自定义拷贝构造函数:实现深拷贝
    StrDeep(const StrDeep& s)
    {
        m_str = new char[strlen(s.m_str)+1]; // 开辟独立的新空间
        strcpy(m_str, s.m_str);              // 复制内容而非指针
    }
    
    ~StrDeep()
    {
        cout<<"~StrDeep"<<endl;
        delete[] m_str; // 安全释放各自独立的内存
    }
    
    void Print()
    {
        cout<<m_str<<endl;
    }
private:
    char *m_str;
};

void testDeepCopy()
{
    StrDeep s1("World");
    StrDeep s2(s1);     // 调用自定义拷贝构造
    
    s1.Print();
    s2.Print();
    // s1 和 s2 拥有不同的内存地址,析构互不影响
}

总结

掌握C++拷贝构造函数是管理对象生命周期的关键。对于包含指针或资源句柄的类,务必遵循“三/五法则”,显式实现拷贝构造函数以确保深拷贝,避免因资源竞争和重复释放导致的内存安全漏洞。以上就是C++拷贝构造函数与深拷贝实现的详细内容,更多关于C++类对象管理的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。