导读:本期聚焦于小伙伴创作的《C++构造函数有哪些?默认拷贝和移动构造函数又是如何工作的?》,敬请观看详情。当你定义一个类却没写任何构造函数时,编译器真的什么都没做吗?实际上C++会按需生成默认构造、拷贝构造、移动构造等函数。默认拷贝构造执行成员逐一复制,若类含指针成员极易引发浅拷贝隐患;移动构造则通过窃取资源提升性能,但仅在未声明特定函数时才自动生成。理解这些隐式函数的触发条件与底层行为,能帮你在资源管理类中避开重复释放与性能损耗,写出更安全的C++代码。

在C++中,构造函数负责对象的初始化。除了我们显式定义的构造函数之外,编译器在特定条件下还会隐式生成几种特殊的构造函数。准确把握它们的存在时机与行为差异,是写出正确且高效C++代码的基础。

C++构造函数有哪些?默认拷贝和移动构造函数又是如何工作的?

一、C++中常见的构造函数类别

C++里的构造函数并不是只有一种。从使用方式和编译器介入程度来看,主要包含以下几种:普通构造函数(含带参和无参)、拷贝构造函数、移动构造函数,以及由编译器生成的默认构造函数。普通构造函数由开发者直接定义,用来根据给定参数初始化对象。

默认构造函数是指无参的构造函数。如果我们没有声明任何构造函数,编译器会自动生成一个平凡的默认构造函数,它仅执行成员变量的默认初始化。一旦你定义了任意一个构造函数,编译器便不再生成默认版本,此时若仍需无参构造,必须显式写 ClassName() = default; 或自行实现。

二、默认构造函数何时生成

编译器生成的默认构造函数属于inline且public的。它会在你定义对象却未提供初始化参数时被调用,例如 MyClass obj;。但如果类中含有引用成员或const成员且未在类内初始化,编译器将无法生成可用的默认构造函数,因为引用和const必须在构造时绑定。

下面示例展示了一个仅由编译器生成默认构造函数的类:

#include <iostream>
#include <string>

class User {
public:
    std::string name;
    int age;
    // 没有声明任何构造函数,编译器生成默认构造函数
};

int main() {
    User u;          // 调用编译器生成的默认构造函数
    u.name = "Tom";
    u.age = 20;
    std::cout << u.name << ":" << u.age << std::endl;
    return 0;
}

上述代码中,User 没有自定义构造,因此 User u; 调用了隐式默认构造函数,string和int被零值或空串初始化。这种机制降低了简单数据聚合类的使用成本。

三、拷贝构造函数的机制与风险

拷贝构造函数用于用同类型的已存在对象来初始化新对象,形式为 ClassName(const ClassName& other)。若未显式声明,编译器会生成默认拷贝构造函数,它对每个非静态成员执行拷贝,内置类型直接复制值,类类型调用其拷贝构造。这种按成员拷贝也叫浅拷贝。

当类管理动态资源(如裸指针)时,浅拷贝会导致两个对象指向同一块内存,析构时重复释放。以下代码演示了隐患:

#include <iostream>
#include <cstring>

class Buffer {
public:
    char* data;
    Buffer(const char* s) {
        data = new char[strlen(s) + 1];
        strcpy(data, s);
    }
    // 未定义拷贝构造,编译器生成默认版本
    ~Buffer() { delete[] data; }
};

int main() {
    Buffer a("hello");
    Buffer b = a;   // 默认拷贝构造,a.data与b.data指向同一地址
    // 离开作用域时 a和b 的析构函数都会 delete[] 同一指针,程序崩溃
    return 0;
}

为避免该问题,需要自定义拷贝构造函数执行深拷贝,或者禁用拷贝、改用移动语义。深拷贝即在构造时重新分配资源并复制内容,保证对象间资源独立。

四、移动构造函数的原理与生成规则

移动构造函数通过右值引用 ClassName(ClassName&& other) 窃取源对象资源,避免不必要的复制。它通常将源对象的指针接管过来,并把源对象置于可安全析构的状态(如置空指针)。移动后源对象不应再被使用其资源。

编译器会在类未声明拷贝构造、拷贝赋值、移动赋值、析构函数时,且成员都可移动时,自动生成移动构造函数。一旦你定义了上述任意一种,移动构造默认不再生成,除非写 = default。下面示例展示移动构造如何提升性能:

#include <iostream>
#include <vector>

class BigData {
public:
    std::vector<int> vec;
    BigData(size_t n) : vec(n, 1) {}
    // 移动构造,接管临时对象资源
    BigData(BigData&& other) noexcept : vec(std::move(other.vec)) {
        std::cout << "move ctor" << std::endl;
    }
    // 拷贝构造
    BigData(const BigData& other) : vec(other.vec) {
        std::cout << "copy ctor" << std::endl;
    }
};

BigData create() {
    BigData tmp(1000);
    return tmp;  // 触发移动或拷贝消除
}

int main() {
    BigData a = create();
    return 0;
}

在支持移动语义的编译环境下,返回局部对象会优先调用移动构造(或被优化掉),极大减少大对象复制开销。注意移动构造应标记为 noexcept,否则标准库容器在扩容时可能因异常安全考虑而放弃移动改用拷贝。

五、构造函数规则总结对照

为了更直观理解,我们用表格列出几类构造函数的生成条件与典型行为:

构造函数类型未自定义时的编译器行为主要风险或注意点
默认构造无任何构造声明时生成含引用或const无初始化则生成失败
拷贝构造未声明时生成,按成员浅拷贝资源管理类易重复释放
移动构造未声明拷贝/移动/析构等时生成需 noexcept 以获得容器优化

综合来看,C++构造函数体系依赖编译器隐式生成规则来平衡易用性与安全性。在编写管理资源的类时,应遵循“五法则”:若需要自定义析构、拷贝或移动中的任意一个,通常五个都要明确处理,或利用 = default= delete 精确控制,防止对象生命周期出现难以排查的bug。

C++构造函数拷贝构造函数移动构造函数修改时间:2026-08-04 20:48:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。