尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Effective C++ 构造/析构/赋值运算 读书笔记 - 实践

Effective C++ 构造/析构/赋值运算 读书笔记 - 实践
📅 发布时间:2026/6/20 17:10:39

目录

一、正常情况下

二、若不想使用编译器自动生成的函数,就该明确拒绝(Effective C++ 06)

三、为多态基类声明virtual析构函数(Effective C++ 07)

四、别让异常逃离析构函数(Effective C++ 08)

五、绝不在构造和析构过程中调用virtual函数(Effective C++ 09)

六、令operator=返回一个reference to *this(Effective C++ 10)

七、在operator=中处理”自我赋值“(Effective C++ 11)

八、复制对象时勿忘其每一个成分(Effective C++ 12)


一、正常情况下

以下函数会自动生成:

#include 
using namespace std;
class Empty {public:Empty() {}                  // 默认构造函数Empty(const Empty& rhs) {}  // 拷贝构造函数 rhs是右操作符缩写~Empty() {}                 // 析构函数// 拷贝赋值运算符// 第一个&:返回类型是引用,第二个&:参数是一个常量对象的引用Empty& operator=(const Empty& rhs) {// this是一个隐含的指针参数,存在于每一个非静态成员函数中// 当你调用一个对象的成员函数的时,编译器会自动把该对象的地址传入thisif(this!=&rhs){// 拷贝成员变量}return *this;}int x;static int count; // 静态成员变量(属于类,全局唯一)// 非静态成员函数的本质,其实是一个普通函数,只是多了一个隐式的“this 指针”参数。// 非静态成员函数,属于对象void show(){ // 等于 void show(Empty* this)cout<<"x="<二、若不想使用编译器自动生成的函数,就该明确拒绝(Effective C++ 06)

下面的例子是编译可以通过,但是可能报错的情况:

#include 
using namespace std;
class File {public:File(const std::string& filename) {cout << "打开文件: " << filename << endl;}~File() { cout << "关闭文件\n"; }// 没有禁拷贝!
};
int main() {File f1("data.txt");File f2 = f1;  // ✅ 编译能过,但逻辑错误!
}
// 可以编译通过
// 编译器会自动生成一个“浅拷贝”构造函数;
// f1 和 f2 都指向同一个文件;
// 当 main() 结束时,~File() 会执行两次;
//  程序可能崩溃(重复关闭同一文件描述符)。


可以明确拒绝默认构造:

#include 
using namespace std;
class File { // File类用来管理文件的打开与关闭
public:File(const std::string& filename) { open(filename); } // 构造函数:当创建FILE对象时,会自动打开~File() { close(); } //析构函数File(const File&) = delete;             // ❌ 禁止拷贝构造File& operator=(const File&) = delete;  // ❌ 禁止赋值
private:void open(const std::string& filename);void close();
};
int main(){File f1("data.txt");// File f2 = f1;   // ❌ 编译器会自动调用拷贝构造函数!return 0;
}

明确拒绝的含义:
不要让编译器帮你做不安全的事情。

如果你知道这个类不应该被拷贝/赋值。

就要用=delete明确告诉编译器禁止生成。

三、为多态基类声明virtual析构函数(Effective C++ 07)

如果一个类打算被当作基类使用(尤其是通过基类指针删除派生类对象),
那它的析构函数就必须是 virtual 的。

问题示例:

#include 
using namespace std;
class Base {public:~Base() { cout << "Base::~Base()\n"; }
};
class Derived : public Base {public:~Derived() { cout << "Derived::~Derived()\n"; }
};
int main() {Base* p = new Derived();// p的静态类型是Base*// 调用delete p;时,编译器只知道它是个Base*// 没有virtual,就不会做动态绑定//所以只调用Base::~Basedelete p;  // ❌ 问题点!
}
// Derived 的析构函数 没有被调用!
// Derived 中的资源(比如动态内存、文件、锁等)不会被释放!
// 导致资源泄漏甚至未定义行为。

当析构函数不是 virtual 时,delete p; 只会调用指针静态类型的析构函数。

正确写法:

#include 
using namespace std;
class Base {public:virtual ~Base() { cout << "Base::~Base()\n"; }
};
class Derived : public Base {public:~Derived() { cout << "Derived::~Derived()\n"; }
};
int main() {Base* p = new Derived();delete p;  // ✅ 正确释放
}

如果你的类打算被继承,会通过基类指针或引用操作对象,尤其通过delete basePtr;删除对象,就必须加virtual.

四、别让异常逃离析构函数(Effective C++ 08)

因为在 C++ 中,如果析构函数在栈展开(stack unwinding)过程中抛出异常,
会导致 程序直接终止(std::terminate())。

栈展开:当异常被抛出后,程序未来找到合适的catch块,会依次退出当前函数调用栈中的函数,并在退出的过程中自动调用已构造对象的析构函数。

简单说,就是从抛出点开始,一层层弹出函数调用栈,清理已创建的局部对象。

错误示例:

#include 
using namespace std;
class Test {public:~Test() { throw runtime_error("析构函数异常!"); } // 3析构函数又抛出了第二个异常
};
void func() {Test t;  // 局部对象throw runtime_error("函数内部异常!");  // 2抛出异常,程序开始栈展开,准备析构局部对象t
}
int main() {try {func();  // 1} catch (const exception& e) {cout << "捕获异常:" << e.what() << endl;}
}

C++ 语言规定:

如果一个异常在栈展开期间再次抛出,程序必须调用 std::terminate()。

原因:

  • 栈展开期间系统已经在处理一个异常;

  • 若又有另一个异常冒出,编译器无法同时处理两个异常;

  • 为保证系统稳定,只能直接终止程序。

#include 
using namespace std;
class Test {public:~Test() {try {danger();  // 可能抛出异常} catch (const exception& e) {cerr << "析构函数捕获异常" << e.what() << endl;}}private:void danger() { throw runtime_error("文件关闭失败"); }// 如果上层函数没有 catch,异常会继续向上层传播,这样可以不让它往上跑
};
void func() {Test t;  // 局部对象throw runtime_error("函数内部异常!");  // 2抛出异常,程序开始栈展开,准备析构局部对象t
}
int main() {try {func();  // 1} catch (const exception& e) {cout << "捕获异常:" << e.what() << endl;}
}

五、绝不在构造和析构过程中调用virtual函数(Effective C++ 09)

因为在构造和析构期间,对象的多态性还没”完全建立”或已经“失效”。

调用虚函数不会发生多态,而只会调用当前类版本的函数。

c++的对象在构造时是分阶段构造的:

1️⃣ 构造 Base 部分(调用 Base::Base())
2️⃣ 再构造 Derived 部分(调用 Derived::Derived())

也就是说,当执行 Base() 构造函数时,
整个对象的“有效类型”其实就是 Base。

错误示例:

正确示例:

如果非要在构造过程中自动调用怎么办?

用工厂函数或静态创建函数。

工厂函数:

把对象的创建过程封装起来,而不是直接在外部用 new 或构造函数创建对象。

六、令operator=返回一个reference to *this(Effective C++ 10)

“让赋值运算符函数返回当前对象本身的引用。”

因为我们希望赋值操作能连续使用。

这样可以支持连锁赋值,就像内建类型(int,double)那样。

a = b = c;

编译器会当成:

a.operator=( b.operator=(c) );

要想这样写能成立,
b.operator=(c) 必须返回 b 自己(的引用),
这样外层的 a.operator=(...) 才能继续执行。

错误写法:

class A {
public:A operator=(const A& rhs) {   // 返回一个“副本”,注意:返回类型是 A(值),不是 A&// 拷贝数据...return *this;             // 返回一个临时对象}
};

这样会发生:

  • (b = c) 返回了一个临时副本;

  • 外层 a = (b = c) 就变成了 a = 临时对象;

  • 临时对象马上销毁;

  • 效率差,行为不符合期望。

解释:多一次拷贝,效率变差

正确写法:

class A {
public:A& operator=(const A& rhs) {  // 返回引用if (this != &rhs) {// 拷贝数据}return *this;             // 返回自己}
};

上面这份代码就不会产生一个临时对象。

七、在operator=中处理”自我赋值“(Effective C++ 11)

a=a ;

a = f(); // f() 恰好返回 a 的引用
这个就是自我赋值,如果没有处理好,就可能在赋值过程中把自己给毁掉了。

加一条:检查自我赋值就可以了。

八、复制对象时勿忘其每一个成分(Effective C++ 12)

当你为一个类写拷贝构造函数或者拷贝赋值运算符时,一定要记得复制对象的所有成员和所有基类部分。

错误示范:

正确示范:

一旦定义了自己的拷贝构造函数或operator=,编译器就不会再自动生成默认版本,而默认版本里才会包含”基类+成员“的完整拷贝逻辑。

所以,当你手动定义了,就必须接住这个责任。

相关新闻

  • 市面上锡膏品牌推荐排行榜单?锡膏品牌 锡膏公司 锡膏产品 锡膏供应厂家 锡膏工厂 锡膏厂家 锡膏生产厂家
  • 可靠的变压器公司口碑推荐榜?变压器公司 变压器品牌 变压器产品 变压器厂商 变压器供应商 变压器渠道
  • 视觉缺陷检测系统公司技术实力盘点:行业应用与服务解析

最新新闻

  • DeepSeek-V4-Flash与V4-Pro技术解析:国产大模型私有化部署新基准
  • Playwright自动化测试四大实战技巧:从MCP思想到工程实践
  • 2026年6月最新雷达中国官方售后电话热线客服地址服务网点 - 亨得利官方服务中心
  • 武汉助产学校:职教升学理想坐标,护理人才培育摇篮 - 辛云教育资讯
  • ComfyUI-VideoHelperSuite终极指南:快速构建专业级视频处理工作流
  • 国内主流卷板机品牌排行:实测性能与服务全维度对比 - 起跑123

日新闻

  • 信任的进化:技术实现详解——如何用JavaScript构建博弈论模拟器
  • Terrakube自定义工作流:如何集成OPA、Infracost等工具扩展IaC能力
  • grunt-concurrent快速入门:5分钟学会并行运行Grunt任务

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号