展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > C++构造函数类型:默认、拷贝与移动构造

C++构造函数类型:默认、拷贝与移动构造

默认构造函数、拷贝构造函数和移动构造函数是C++对象初始化的核心机制。默认构造函数在无参数时创建对象,若未定义任何构造函数,编译器会隐式生成,但不初始化内置类型成员;显式使用=default可保留默认构造,=delete可禁止。拷贝构造函数ClassName(constClassName&)用于通过现有对象创建新对象,执行成员逐个拷贝,对资源管理类需显式定义以实现深拷贝,避免浅拷贝导致的双重释放;若定义了析构函数、拷贝或移动操作之一,编译器不再生成隐式拷贝构造。移动构造函数ClassName(Cl

本文目录

    默认构造函数、拷贝构造函数和移动构造函数是C++对象初始化的核心机制。默认构造函数在无参数时创建对象,若未定义任何构造函数,编译器会隐式生成,但不初始化内置类型成员;显式使用=default可保留默认构造,=delete可禁止。拷贝构造函数ClassName(const ClassName&)用于通过现有对象创建新对象,执行成员逐个拷贝,对资源管理类需显式定义以实现深拷贝,避免浅拷贝导致的双重释放;若定义了析构函数、拷贝或移动操作之一,编译器不再生成隐式拷贝构造。移动构造函数ClassName(ClassName&&)是C++11引入的优化机制,通过转移右值资源提升性能,执行成员逐个移动,适用于临时对象或std::move场景;其隐式生成条件更严格,需类未定义特殊成员函数且所有成员可移动。三者的选择涉及效率与语义:拷贝保证独立性但代价高,移动高效但使源对象进入未指定状态。遵循Rule of Zero(依赖编译器生成)或Rule of Five(显式定义五种特殊成员函数)是管理资源和确保正确性的关键实践。构造函数中常见陷阱是未初始化内置成员,应使用成员初始化列表确保安全初始化。

    C++构造函数类型 默认拷贝移动构造

    C++中,构造函数是对象生命周期中至关重要的一环,它负责在对象创建时进行初始化。而默认构造函数、拷贝构造函数和移动构造函数,构成了理解C++对象构造行为的基石。它们各自在不同场景下扮演着独特的角色,理解它们的工作原理,对于编写健壮、高效且避免资源泄漏的代码来说,是绝对必要的。

    解决方案

    理解C++中的构造函数类型,尤其是默认、拷贝和移动构造函数,是掌握对象生命周期管理的关键。它们各自处理对象创建的不同情境,并且在很多情况下,编译器会默默地为我们生成它们。

    默认构造函数 (Default Constructor) 默认构造函数是没有参数的构造函数。它的主要职责是确保对象在创建时处于一个可用的状态。

    • 何时出现:
      • 如果你没有为类定义任何构造函数,编译器会自动为你生成一个隐式的默认构造函数。这个隐式版本会调用其基类的默认构造函数,并对所有类类型的非静态成员调用它们的默认构造函数。对于内置类型(如int, double, 指针等),它不会进行任何初始化,这常常是导致未定义行为的陷阱。
      • 你可以显式地定义一个无参构造函数。
      • 你也可以使用ClassName() = default;来显式请求编译器生成默认构造函数,即使你已经定义了其他带参数的构造函数。这在某些情况下非常有用,比如当你定义了一个带参构造函数后,想保留一个无参构造函数。
    • 作用: 主要用于创建没有特定初始化参数的对象,例如MyClass obj;。

    拷贝构造函数 (Copy Constructor) 拷贝构造函数用于通过一个已存在的同类型对象来创建一个新对象。它本质上是“复制”一个对象。

    • 签名: ClassName(const ClassName& other);
    • 何时出现:
      • 如果你没有定义拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符或析构函数,编译器会为你生成一个隐式的拷贝构造函数。
      • 这个隐式版本会执行“成员逐个拷贝”(member-wise copy),也就是浅拷贝。对于内置类型成员,直接复制其值;对于类类型成员,调用其拷贝构造函数。
      • 当你的类管理着动态分配的资源(比如指针指向的堆内存)时,隐式的浅拷贝会导致严重的问题,比如双重释放或悬垂指针。这时,你就需要显式定义一个拷贝构造函数来进行深拷贝。
    • 作用: 用于以下场景:
      • MyClass obj2 = obj1;
      • MyClass obj3(obj1);
      • 将对象作为参数按值传递给函数。
      • 函数返回一个对象。

    移动构造函数 (Move Constructor) 移动构造函数是C++11引入的,用于通过“窃取”一个右值(通常是临时对象或即将销毁的对象)的资源来构造新对象,而不是复制。它旨在提高性能,尤其是在处理大型对象或资源密集型对象时。

    • 签名: ClassName(ClassName&& other);
    • 何时出现:
      • 如果你没有定义拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符或析构函数,并且所有非静态数据成员和基类都可移动构造时,编译器会为你生成一个隐式的移动构造函数。
      • 隐式版本执行“成员逐个移动”(member-wise move)。
      • 与拷贝构造函数类似,如果你的类管理动态资源,你通常需要显式定义移动构造函数,以确保资源正确地从源对象转移到目标对象,并将源对象置于一个有效但未指定的状态(通常是“空”状态)。
    • 作用: 用于以下场景:
      • MyClass obj2 = std::move(obj1); (将obj1的资源转移给obj2)
      • 函数返回一个右值对象(如临时对象)。
      • 将右值作为参数按值传递给函数。

    编译器何时会“偷偷”为你生成这些构造函数?(以及你何时应该阻止它)

    这确实是一个常常让人感到困惑的地方,毕竟编译器在背后做了不少工作,但我们又不能完全依赖它的“好意”。理解这些隐式行为的触发条件,以及如何介入,是编写可靠C++代码的关键。

    默认构造函数的隐式生成与控制

    编译器为你生成一个隐式默认构造函数的条件非常简单:只要你没有为类定义任何构造函数(无论是默认的、带参数的、拷贝的还是移动的),编译器就会生成一个。这个隐式生成的构造函数,对于内置类型成员,它不会进行初始化,这常常是导致未定义行为的温床。比如:

    class MyData {
    public:
        int value; // 内置类型
        std::string name; // 类类型
    };
    
    // MyData obj; // 此时value的值是未知的,name会调用std::string的默认构造函数初始化为空字符串

    何时阻止它:

    • 定义了其他构造函数: 一旦你定义了任何一个构造函数(比如一个带参数的构造函数),编译器就不会再为你生成隐式默认构造函数了。如果你仍然需要一个无参构造函数,就必须显式地定义它,或者使用= default。
      class MyData {
      public:
          int value;
          std::string name;
          MyData(int v) : value(v) {} // 定义了带参构造函数
          // MyData obj; // 错误:没有可用的默认构造函数
          // MyData() = default; // 这样就可以再次拥有默认构造函数了
      };
    • 明确禁止: 如果你希望你的类对象总是需要参数才能构造,或者根本不希望它能被默认构造,可以使用= delete。
      class MyData {
      public:
          int value;
          MyData(int v) : value(v) {}
          MyData() = delete; // 明确禁止默认构造
      };
      // MyData obj; // 编译错误

    拷贝构造函数的隐式生成与控制

    编译器生成隐式拷贝构造函数的条件稍微复杂一些:当你没有定义拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符或析构函数时,编译器就会生成一个。这个隐式版本执行的是浅拷贝,即成员逐个复制。对于简单的值类型成员,这通常没有问题。但对于管理资源(如动态内存)的类,这就是个大麻烦。

    class BadString {
    public:
        char* data;
        BadString(const char* s) {
            size_t len = strlen(s);
            data = new char[len + 1];
            strcpy(data, s);
        }
        ~BadString() {
            delete[] data;
        }
        // 没有定义拷贝构造函数
    };
    
    // BadString s1("hello");
    // BadString s2 = s1; // 隐式拷贝构造,s1.data和s2.data指向同一块内存!
    // s1.~BadString(); // 释放内存
    // s2.~BadString(); // 再次释放同一块内存,导致双重释放错误!

    何时阻止它:

    • 定义了任何资源管理相关的特殊成员函数: 只要你定义了拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符或析构函数中的任何一个,编译器就不会再为你生成隐式拷贝构造函数了。这就是所谓的“Rule of Three/Five”的核心思想。
    • 明确禁止: 如果你希望你的类对象不能被拷贝(例如,它代表一个独占的资源句柄),可以使用= delete。
      class UniqueHandle {
      public:
          // ... 构造函数等
          UniqueHandle(const UniqueHandle&) = delete; // 禁止拷贝构造
          UniqueHandle& operator=(const UniqueHandle&) = delete; // 禁止拷贝赋值
      };
      // UniqueHandle h1;
      // UniqueHandle h2 = h1; // 编译错误

    移动构造函数的隐式生成与控制

    编译器生成隐式移动构造函数的条件与拷贝构造函数类似,但更严格:当你没有定义拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符或析构函数时,并且所有非静态数据成员和基类都可移动构造时,编译器才会生成一个。它执行的是成员逐个移动。

    class MyVector {
    public:
        int* arr;
        size_t size;
        // ... 构造函数、析构函数、拷贝构造函数等
        // 没有显式定义移动构造函数
    };
    // std::vector vec;
    // vec.push_back(MyVector(some_size)); // 如果MyVector可移动,这里可能发生隐式移动

    何时阻止它:

    • 定义了任何资源管理相关的特殊成员函数: 同样地,只要你定义了拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符或析构函数中的任何一个,编译器就不会再为你生成隐式移动构造函数了。
    • 明确禁止: 如果你希望你的类对象不能被移动(这在实践中比较少见,因为移动通常是优化),可以使用= delete。
      class NonMovable {
      public:
          // ...
          NonMovable(NonMovable&&) = delete; // 禁止移动构造
          NonMovable& operator=(NonMovable&&) = delete; // 禁止移动赋值
      };

    总的来说,编译器在背后做了很多工作,但我们作为开发者,需要清楚它何时会介入,以及何时我们需要接管控制权。尤其是当类管理着外部资源时,显式地定义或禁止这些特殊成员函数,是确保程序正确性和效率的基石。

    拷贝构造与移动构造:效率与语义的抉择

    在C++的世界里,拷贝构造和移动构造就像是双生子,它们都旨在从一个现有对象创建另一个新对象,但它们的内在机制和目的却大相径庭。我个人觉得,理解它们之间的差异,是真正驾驭现代C++性能优化的一把钥匙。

    核心区别:复制 vs 转移

    • 拷贝构造函数: 它的核心思想是“复制”。它会创建一个源对象的完整副本,这意味着新对象将拥有自己独立的一套资源。如果源对象管理着堆内存,那么拷贝构造函数通常需要分配新的堆内存,并将源对象的数据复制过去。这保证了源对象和新对象是完全独立的实体,互不影响。
    • 移动构造函数: 它的核心思想是“转移”或“窃取”。它不是复制资源,而是将源对象所拥有的资源(比如堆内存的指针、文件句柄等)直接转移给新对象。完成转移后,源对象通常会被置于一个“空”或“无效”的状态,这样它的析构函数就不会去释放已经被转移的资源。

    效率考量:深拷贝的代价

    当涉及到管理动态内存或大型数据结构时,拷贝构造函数执行的深拷贝操作往往是昂贵的。它通常涉及:

    1. 内存分配: 为新对象分配与源对象相同大小的内存。
    2. 数据复制: 将源对象内存中的数据逐字节复制到新分配的内存中。

    这两个步骤都可能消耗显著的CPU时间和内存带宽。想象一下,一个std::vector包含了百万个整数,每次拷贝都需要重新分配并复制这百万个整数,这无疑会成为性能瓶颈。

    相比之下,移动构造函数通常只需要执行以下操作:

    1. 指针/句柄赋值: 将源对象的资源指针直接赋给新对象。
    2. 源对象置空: 将源对象的资源指针置为nullptr或等效的“空”状态。

    这个过程通常只是几次指针赋值,其开销几乎可以忽略不计,效率远高于深拷贝。

    语义考量:对象状态的变化

    • 拷贝语义: 拷贝操作完成后,源对象的状态保持不变,它和新对象是两个独立的、内容相同的实体。你可以继续安全地使用源对象。
      std::vector original = {1, 2, 3};
      std::vector copy = original; // 拷贝构造
      // original 仍然是 {1, 2, 3}
      // copy 也是 {1, 2, 3}
    • 移动语义: 移动操作完成后,源对象通常处于一个“有效但未指定”的状态。这意味着你不能再依赖源对象原有的内容或资源。通常,它会被置空,或者其内部状态被清零。尝试在移动后使用源对象可能导致未定义行为,或者至少不再是期望的状态。
      std::vector original = {1, 2, 3};
      std::vector moved = std::move(original); // 移动构造
      // original 现在通常是空的 {},或者至少不能再依赖其内容
      // moved 现在是 {1, 2, 3}

      这就像你把一本书从一个书架A搬到书架B,书架A现在是空的,书在书架B上。

    何时抉择:实际应用场景

    • 选择拷贝:
      • 当你需要两个完全独立、互不影响的对象时。
      • 当你需要保留源对象的数据,同时又需要一个副本进行操作时。
      • 作为函数参数按值传递,且函数内部需要独立修改副本时。
    • 选择移动:
      • 当源对象是一个临时对象,它即将被销毁,其资源不再需要保留时(例如函数返回一个大对象)。
      • 当你明确知道源对象在操作后不再需要其资源时,可以通过std::move()显式地请求移动。这在填充容器、交换对象内容等场景中非常有用。
      • 在一些容器操作(如std::vector的push_back扩容)中,如果元素类型支持移动语义,将自动使用移动构造来避免不必要的拷贝,从而提高效率。

    Rule of Three/Five/Zero:指导原则

    为了更好地管理这些构造函数,C++社区提出了“Rule of Three/Five/Zero”:

    • Rule of Zero: 如果你的类不管理任何资源(即所有成员都是内置类型或标准库类型,如std::string, std::vector等),那么就让编译器自动生成所有特殊成员函数。这是最理想的情况,因为编译器通常能做得比你更好。
    • Rule of Three: 如果你的类需要显式定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,那么很可能你需要定义所有这三个。这通常发生在你的类管理着某种原始资源(如new/delete分配的内存、文件句柄等)时。
    • Rule of Five: 随着C++11引入移动语义,如果你的类遵循Rule of Three,那么你很可能也需要显式定义移动构造函数和移动赋值运算符,以充分利用移动语义带来的性能优势。

    我的经验是,大部分时候,我们应该努力遵循Rule of Zero。如果不得不管理资源,那么Rule of Five就是你的指路明灯。不要吝啬为你的资源管理类实现完整的五大特殊成员函数,这能帮你避免无数的bug和性能问题。

    构造函数中的那些“坑”与最佳实践

    构造函数,作为对象生命的起点,其重要性不言而喻。但正如任何强大的工具一样,它也充满了潜在的陷阱。我个人在项目中就踩过不少关于构造函数的坑,所以总结一些最佳实践,希望能帮大家少走弯路。

    默认构造函数的“坑”:未初始化成员

    最大的坑就是前面提到的,编译器生成的隐式默认构造函数不会初始化内置类型成员。这会导致你的对象在创建后,其某些成员的值是随机的,使用它们就会触发未定义行为。这就像你拿到一个新工具,但它的一些部件是随机组装的,用起来随时可能出问题。

    class MyPoint {
    public:
        int x, y; // 内置类型成员
        // 隐式默认构造函数不会初始化x和y
    };
    
    // MyPoint p; // p.x 和 p.y 的值是未知的!
    // std::cout << p.x << std::endl; // 未定义行为

    最佳实践:显式初始化

    • 成员初始化列表 (Member Initializer List): 这是C++中初始化成员的首选方式,尤其对于const成员、引用成员以及没有默认构造函数的类类型成员。它在构造函数体执行之前完成初始化,效率更高,也更安全。
      class MyPoint {
      public:
          int x, y;
          MyPoint() : x(0), y(0
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    Aperty macOS版
    Aperty macOS版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw macOS版
    AionClaw macOS版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy macOS版
    Loomy macOS版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirBuddy 3 macOS版
    AirBuddy 3 macOS版

    AirBuddy 3 是面向 macOS 的蓝牙外设增强工具,可集中查看 AirPods、Beats 与苹果设备电量,提供快速连接、播放控制、电量提醒、桌面小组件和多台 Mac 外设接力等功能。

    AirServer macOS版
    AirServer macOS版

    AirServer macOS版可把 Mac 转换为无线投屏接收端,通过 AirPlay 或 Google Cast 接收手机、平板和电脑画面,并支持多设备同时连接、高清镜像及会话录制,适合家庭娱乐、课堂教学、会议演示和应用展示。

    AnyDesk macOS版
    AnyDesk macOS版

    AnyDesk macOS版是一款适用于远程办公、设备维护和客户支持的远程桌面工具,可连接并操作异地计算机,支持文件传输、无人值守访问、会话权限控制及跨平台协作。

    WINDOWS 更多
    讯飞绘文 网页版
    讯飞绘文 网页版

    讯飞绘文网页版是科大讯飞推出的AI图文创作与内容运营工具,覆盖热点选题、文章生成、稿件校对、智能配图、排版发布和数据复盘等环节,适合自媒体创作者及内容团队在线使用。

    Maya(Autodesk Maya) Windows版
    Maya(Autodesk Maya) Windows版

    Autodesk Maya 是面向影视、电视、动画和游戏行业的专业三维制作软件,集多边形与 NURBS 建模、角色绑定、关键帧和动作捕捉动画、Bifrost 特效模拟、材质开发及 Arnold 渲染于一体,

    Aperty Windows版
    Aperty Windows版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw Windows版
    AionClaw Windows版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy Windows版
    Loomy Windows版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirServer Windows版
    AirServer Windows版

    AirServer Windows版可把电脑变成AirPlay、Google Cast和Miracast无线投屏接收端,适合会议演示、课堂教学及家庭大屏分享。发送设备通常无需另装客户端,并可同时接收和智能排列多个镜像会话。