admin管理员组文章数量:1566220
文章目录
- 一、自己习惯c++
- 视c++为一个语言联邦
- 尽量以 const,enum,inline 替换 #define
- 尽可能使用const
- 确定对象初始化
- 二、构造,析构,赋值
- 了解c++默默编写并调用那些函数
- 不自动生成的函数,就明确拒绝
- 多态基类声明 virtual 析构函数
- 别让异常逃离析构函数
- 不在构造析构过程中调用virtual函数
- operator= 返回 reference to *this
- operator= 处理‘自我赋值’
- 复制对象勿忘其每一个成分
- 三、资源管理
- 以对象管理资源
- 资源管理类中小心coping行为
- 在资源管理类中提供对原始资源的访问
- 承兑使用new和delete是要采取相同形式
- 独立语句将newed对象植入智能指针
- 四、设计与声名
- 接口正确使用,不易被误用
- 设计class犹如设计type
- 传引用替代传值
- 必须返回对象时,别返回引用
- 成员变量声明为private
- 宁以non-member,non-friend 替换member函数
- 所有参数皆需类型转换,采用non-member 函数
- 考虑写出一个不抛异常的 swap 函数
- 五、实现
- 尽可能延后变量定义是的出现时间
- 尽量少做转型动作
- 避免返回handles指向对象内部成分
- 为“异常安全”而努力是值得的
- 透彻了解inlining的里里外外
- 文件间的编译依存关系降至最低
- 六、继承与面向对象设计
- 确定你的 public 继承塑模出 is-a 关系
- 避免遮掩继承而来的名称
- 区分接口继承和实现继承
- 考虑virtual函数以外的其他选择
- 不重新定义继承而来的non-virtual函数
- 不重定义继承而来的缺省参数值
- 通过复合塑模出 has-a 或 “ 根据某物实现 ”(is-implemented-in-terms-of)
- 明智而审慎的使用private继承
- 明智而审慎地使用多重继承
- 七、模板与泛型编程
- 了解隐式接口和编译期多态
- 了解 typename 双重意义
- 学习处理模板化基类内的名称
- 将与参数无关的代码抽离 templates
- 运用成员函数模板接受所有兼容类型
- 需要类型转换时请为模板定义非成员函数
- 使用 traits classes 表现类型信息
- 认识 template 元编程
- 八、定制 new 和 delete
- 了解 new-handler 的行为
- 了解 new 和 delete 合理替换时机
- 编写 new 和 delete 时需固守常规
- new 与 delete 成对出现
- 九、杂项讨论
- 不要轻忽编译器的警告
- 熟悉 TR1 在内的标准程序库
- 熟悉 boost
一、自己习惯c++
视c++为一个语言联邦
尽量以 const,enum,inline 替换 #define
使用 #define 的缺点:
- 被 #define 定义的宏名称可能在编译器开始处理源码之前被预处理器移走,导致该名称没有进入记号表(symbol table),在需要调试代码时追踪这个宏时不会显示宏名称,调试时就可能找不到
- #define 无法创建一个 class 专属常量,也不提供任何封装性, 宏一旦被定义,它在编译过程中会一直存在,直至被 #undef
其他需要注意的:
- 对 enum 跟 #define 取地址不合法
- enum 和 #define 不会导致非必要的内存分配
总结:
- 对于单纯常量,最好以 const 对象或 enum 替换 #define
- 对于形似函数的宏, 最好该用 inline 函数替换 #define
尽可能使用const
const 可以作用于:
- 在类(classes)外部修饰全局(global)或 namespace 作用域中的常量
- 修饰文件、函数或区块作用域(block scope)中 static 对象
- 修饰类 (classes)内部的 static 和 non-static 成员变量
- 修饰指针自身、指针所指变量
const 作用于指针时需注意:
- const 在 ‘ * ’ 星号左边(写在类型之前或类型之后并无区别),表示被 const 修饰的为 指针所指的变量 而不是指针本身。
- const 在 ‘ * ’ 星号右边,表示被 const 修饰的为指针本身
STL 迭代器是以指针为根据塑膜出来的,就像是 T* 指针。
const 作用于迭代器时需注意:
- 修饰对象为迭代器本身:const T::iterator
- 修饰迭代器所指对象:const_iterator
其他需要注意:
- const 成员函数不可以更改对象内任何 non-static 成员变量(因为 C++ 对常量性的定义 - bitwise constness / physical constness)
bitwise constness (physical constness) 指 const 成员函数只有在不更改对象的任何成员变量(任何一个 bit)时才说是 const
logical constness 指 const 成员函数可以在客户端侦测不出问题的情况下修改对象内的某些 bit
- 不要在 const 成员函数内调用 non-const 成员函数(const 成员函数不改变对象的逻辑状态)
总结:
- const 可以帮助编译器侦测出错误用法。const 可以修饰在作用域内的对象、函数参数、函数返回类型、成员函数本体。
- 编译器强制实施 bitwise constness,但编写程序时应使用“logical constness”
- 当 const 和 non-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可避免代码重复。
确定对象初始化
- C++ 的对象成员变量的初始化动作发生在进入构造函数本体之前,发生在这些成员变量的 default 构造函数被自动调用的时候
- 构造函数中初始化使用成员初值列替换函数中的赋值操作,提高效率,避免在对象初始化之前使用这些成员
- 基类的成员初始化更早于子类
- class 的成员变量以声明次序进行初始化,即使在成员初值列中次序不同,也不会有影响
总结:
- 为内置型对象进行手工初始化(如:C part of C++),C++不保证初始化它们
- 构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量在排列次序上应与它们在 class 中声明的次序相同
- 为免除 “ 跨编译单元之初始化次序 ” 问题,应以 local static 对象替换 non-local static 对象(提供函数返回 local static 对象)
二、构造,析构,赋值
了解c++默默编写并调用那些函数
- 编译器会自动为空类声明:copy 构造函数、copy assignment 操作符、析构函数、default 构造函数(在没有构造函数时才会被声明)。这些函数都是 public 且 inline,仅有当这些函数被调用时才会被编译器创建出来
不自动生成的函数,就明确拒绝
- 直接将函数声明为 private,但 menber 和 friend 仍可以调用
- 设计一个 Uncopyable 类,将 copy 构造函数和 copy assignment 操作符声明为 private
- Boost 里的 noncopyable
- C++11 中的 = delete
多态基类声明 virtual 析构函数
- 带多态性质的基类应该声明一个 virtual 析构函数。如果 class 带有任何 virtual 函数,他就应该拥有一个 virtual 析构函数。
- Classes 的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明 virtual 析构函数。
问题:
- 为什么基类要声明 virtual 析构函数?
如果基类没有声明 virtual 析构函数,在 delete obj 的时候会导致只调用了基类的 non-virtual 析构函数而没有调用子类的析构函数,没有对一些子类对象释放内存,从而造成了内存泄漏 - 为什么基类没有声明 virtual 析构函数会造成内存泄漏?
析构函数跟普通成员没有什么不同,只是编译器在会在特定的时候自动调用析构函数(离开作用域或者执行delete操作);
对于一个成员函数调用(不论是通过对象obj.func还是通过对象指针obj->func),到底是直接调用还是通过虚函数表调用,在编译的时候是确定的,取决于这个函数是不是虚函数;
如果析构不声明为虚函数,那么delete pBase,就被编译器解释为 Base::~Base,否则被编译器解释为 this->virtual_function_table(析构在虚函数表的偏移)
别让异常逃离析构函数
- 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
- 如果需要对某个操作函数运行期间抛出异常做出反应,那么 class 应该提供一个普通函数(而非在析构函数中)执行该操作。
不在构造析构过程中调用virtual函数
- 在调用子类的构造函数体期间,会先调用基类的构造函数,而此时子类并未实例化,所以基类的构造函数中调用的 virtual 函数并不会调用到子类所重写的函数,而是调用基类的函数
- 所以在构造和析构期间不要调用 virtual 函数,因为这类调用从不下降至子类
operator= 返回 reference to *this
operator= 处理‘自我赋值’
- 确保当对象自我复制时 operator= 由良好的行为。其中技术包括 比较 “ 来源对象 ” 和 “ 目标对象 ” 的地址、调整代码的顺序、以及 copy-and-swap
复制对象勿忘其每一个成分
- Copying 函数应该确保复制 “ 对象内的所有成员变量 ” 及 “ 所有 base class 成员变量 ”
- 不要尝试以某个 copying 函数实现另一个 copying 函数。应该将共同的代码放入另一个函数中,供这两个 coping 函数调用
三、资源管理
以对象管理资源
- 获得资源后立刻放进管理对象(managing object)内(对象构造函数初始化或赋值)
- 管理对象运用析构函数确保资源被释放
其他:
- auto_ptr 通过 copy 构造函数或 copy assignment 操作符复制时,被复制对象会变为 null,使得复制所得的指针拥有资源的唯一拥有权。
- auto_ptr 和 shared ptr 的析构函数内都为 delete 而不是 delete[]
- 数组指针可用 Boost 的 scoped_array 和 shared_array
总结:
- 为了防止内存泄漏,使用智能指针对象,它们在构造函数中获得资源并在析构函数中释放资源
- shared_ptr 较为常用,因为 auto_ptr 在复制时会使本身指向null。C++11 中,shared_ptr 的循环引用问题可用 weak_ptr 解决
智能指针详细解析(RAII)
资源管理类中小心coping行为
- 复制 RAII 对象必须一并复制它所管理的资源(深拷贝),所以资源的 copying 行为决定 RAII 对象的 copying 行为
- 普遍常见的 RAII class copying 行为:抑制 copying、引用计数法
其他:
- shared_ptr 允许指定 “ 删除器 ” (deleter)
在资源管理类中提供对原始资源的访问
- API 往往要求访问原始资源,所以每一个 RAII class 应该提供一个获取 “ 原指针 ” 的方法
- 对原始资源的访问有显式转换和隐式转换。一般显式转换比较安全,但隐式转换比较方便
承兑使用new和delete是要采取相同形式
- new 数组时用 delete[],new 不是数组时不要用 delete[]
独立语句将newed对象植入智能指针
- 以独立语句将 new 后的对象置入智能指针。如果不这样做,在 new 对象后,未置入智能指针前,如果抛出异常,可能会导致内存泄漏
四、设计与声名
接口正确使用,不易被误用
- 好的接口应该是容易被正确使用,不容易被误用。如入参等
- “ 促进正确使用 ” 的办法有接口的一致性,以及与内置类型的行为兼容(智能指针)
- “ 阻止误用 ” 的办法有建立新类型、限制类型上的操作,约束对象值,以及消除客户的资源管理责任
- shared_ptr 支持定制型删除器(custom deleter),这可防范 “ cross-DLL problem ” ,可被用来自动解除互斥锁
设计class犹如设计type
Class 的设计就是 type 的设计,需要注意:
- 新 type 的对象应该如何被创建和销毁?
- 对象的初始化和对象的赋值该有什么样的差别?
- 新 type 的对象如果被值传递,意味着什么?
- 什么是新 type 的 " 合法值 " ?
- 新 type 需要配合某个继承图系吗?
- 你的新 type 需要什么样的转换?
- 什么样的操作符和函数对新 type 而言是合理的吗?
- 谁该取用新 type 的成员?
- 什么是新 type 的 “ 未声明接口 ” ?
- 你的新 type 有多么一般化
- 你真的需要一个新 type 吗?
具体请参考原文
传引用替代传值
- 进行值传递时都是以实际实参的副本为初值,这些副本都是由对象的 copy 构造函数创建,即还要再次调用析构函数,是个费时的操作。
- 以引用的方式传递可以避免值传递问题
- 引用传递也可以避免对象切割问题。
- 对于内置类型(int…),以及 STL 的迭代器和函数对象,值传递更为合适
必须返回对象时,别返回引用
- 绝不要返回一个指向 local stack 对象的 pointer 或 reference ,local 对象会在退出函数前被销毁
- 绝不要返回一个指向 heap-allocated 对象的 reference,需要考虑什么时候去实现 delete
- 绝不要返回一个指向 local static 对象的 pointer 或 reference ,有可能同时需要多个这样的对象,需要过多内存
成员变量声明为private
- 将成员变量声明为 private 。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供 class 作者以充分的实现弹性。
- protected 并不比 public 更具封装性,如子类
从封装性看,只有两种访问权限:private(提供封装)和其他(不提供封装)
宁以non-member,non-friend 替换member函数
- namespace 和 classes 不同,namespace 可跨越多个源码文件而 class 不能
- 用 non-menber non-friend 函数替换 menber 函数,可以增加封装性、packaging flexibility 封装弹性 和机能扩充性
所有参数皆需类型转换,采用non-member 函数
- 如果某个函数需要把所有参数进行类型转换(包括被 this 指针所指的哪些隐喻参数),那么这个函数必须是个 non-member
- 不要用 friend 代替 non-member
考虑写出一个不抛异常的 swap 函数
- 当 std::swap 对你的 class 效率不高时,提供一个 swap 成员函数,并确定这个函数不抛出异常
- member 的 swap 绝不应该抛出异常。因为 swap 缺省版本是以 copy 构造函数和 copy assignment 操作符为基础
- 如果你提供一个 member swap,也该提供一个 non-member swap 用来调用前者(对于 classes 而非 templates),并且特化 std::swap
- 调用 swap 时应针对 std::swap 使用 using 声明式,然后调用 swap 并且不带任何 “ namespace 修饰 ”
- 为 “ 用户定义类型 ” 进行 std templates 全特化是最好的,但千万不要在 std 内加入新内容
详情请看原文
五、实现
尽可能延后变量定义是的出现时间
- 尽可能延后变量定义式,这样做可增加程序的清晰度并改善程序效率
- 在循环中,应把变量定义于循环外还是循环内
做法A:1个构造函数 + 1个析构函数 + n个赋值操作
做法B:n个构造函数 + n个析构函数
除非你知道 copy 成本比 “ 构造 + 析构 ” 成本低,且你正在处理代码中效率高度敏感的部分(对效率高要求),否则应该使用做法B(将变量定义于循环内)
尽量少做转型动作
C++ 四种转型:
- const_cast 通常用来转除对象的常量性
- dynamic_cast 主要用来 “ 安全向下转型 ” (用来决定某对象是否归属继承体系中的某个类型???)执行速度较慢
- reinterpret_cast 执行低级转型,实际结果可能取决于编译器,如 pointer to int 转为 int
- static_cast 用来向上转型等隐式转换,如 non-const 对象转为 const 对象、int 转 double 等
总结
- 在注重效率的代码中避免使用 dynamic_cast
转为
或者在 base class 里提供 virtual 函数
- 如果转型是必要的,试着将它隐藏在某个函数背后。这样可以调用该函数而不需将转型放进代码内
- 宁可使用 C++ - style 转型(四种转型),不要使用旧式转型如(double)int
避免返回handles指向对象内部成分
- 避免返回指向对象内部的 handles(包括 references、指针、迭代器),避免发生 dangling handles(虚吊地址),增加封装性
为“异常安全”而努力是值得的
“ 异常安全 ” 由两个条件:
- 不泄露任何资源
- 不允许数据败坏
异常安全函数需具备以下三个保证之一:
- 基本承诺:如果异常被抛出,程序内的任何事物仍然保持有效状态,没有任何对象或数据结构因此败坏,所有对象都处于一种内部券后一致的状态。
- 强烈保证:如果异常被抛出,程序状态不改变。如果函数失败,程序会恢复到 “ 调用函数之前 ” 的状态
- 不抛出异常:承诺绝不抛出异常
总结:
- 异常安全函数即使发生异常与不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型
- “ 强烈保证 ” 往往能够以 copy-and-swap 实现,但 “ 强烈保证 ” 并非对所有函数都可实现或具备现实意义
- 函数提供的 “ 异常安全保证 ” 通常采取木桶效应,取最弱的
透彻了解inlining的里里外外
inline 缺点:
- 增加代码量,造成程序体积太大
- 造成的代码膨胀可能导致额外的换页行为,降低指令高速缓存装置的命中率,造成效率损失
- inline 函数无法随着程序库的升级而升级,需要重新编译用到该函数的客户端
inline 优点:
- 不直接将代码置入调用处,避免了调用函数的寻址操作
其他:
- inline 修饰 virtual 函数会出现不定数,inline 在编译时确认,virtual 在运行时确认,结果根据编译器决定
- 使用函数指针指向 inline 函数可能导致 inline 属性失效
总结:
- 将大多数 inline 修饰在小型、被频繁调用的函数身上,这可使日后的调试过程和代码升级(二进制升级 binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
- 不要只因为 function templates 出现在头文件,就将它们声明为 inline
- 不要给 virtual 函数声明 inline
- 不要给析构和构造函数声明为 inline,可能在基类和子类对象创建中出现大量重复代码
文件间的编译依存关系降至最低
“ 接口与实现分离 ” :
- 关键在于以 “ 声明的依存性 ” 替换 “ 定义的依存性 ”
- 如果可以使用 object references 或 object pointers,就不要使用 object
- 尽量以 class 声明式替换 class 定义式,即使函数使用值传递或是对象返回值
- 为声明式和定义式提供不同的头文件
总结:
- 支持 “ 编译依存性最小化 ” 的一般构想是:相依于声明式,不要相依于定义式(Handle classes 和 Interface classes)
- 程序库头文件应该以 “ 完全且仅有声明式 ” 的形式存在(不论是否涉及 templates)
建议看看原文
六、继承与面向对象设计
确定你的 public 继承塑模出 is-a 关系
classes 之间的关系有 is-a 、has-a 和 is-implemented-in-terms-of(根据某物实现)
- “ public 继承 ” 以为 is-a 。适用于 base classes 身上的每一件事情一定也适用于 derived classes 身上,因为每一个 derived class 对象也都是一个 base class 对象(大概意思应该是设计 base classes 时要思考所要应用于什么样的 derived classes 上,不适用于 derived classes 的操作直接在编译阶段检测出来更好)
避免遮掩继承而来的名称
- 基于 public 继承下,如果继承的 base class 中有重载函数,而我们想重新定义或覆盖重写其中一部分,那么我们需要在 derived class 里 using 声明那些会被覆盖重写的名称
- 若是在 private 继承下,则是用 “ 转交函数 ”(forwarding function)
总结: - derived classes 内的名称会隐藏 base classes 内的名称
- 为了使用某些被遮掩的名称,可以使用 using 声明式或转交函数
区分接口继承和实现继承
这章主要讲 virtual 各种用法
- 接口继承和实现继承不同。在 public 继承之下,derived classes 总是继承 base class 的接口
- pure virtual 函数只具备指定接口继承
- impure virtual 函数具体指定接口继承及缺省(默认)实现继承
- non-virtual 函数具体指定接口继承及强制性实现继承
考虑virtual函数以外的其他选择
- 使用 non-virtual interface(NVI)手法,那是 Template Method 设计模式的一种特殊形势。它以 public non-virtual 成员函数 调用 较低访问性(private 或 protected)的 virtual 函数
- 将 virtual 函数替换为 “ 函数指针成员变量 ”,这是 Strategy 设计模式的一种分解表现形式
- 以 trl::function 成员变量替换 virtual 函数,因而允许使用任何可调用物搭配一个兼容于需求的声明式。这也是 Strategy 设计模式的一种形式
- 将继承体系内的 virtual 函数替换为另一个继承体系内的 virtual 函数,这是 Strategy 设计模式的传统实现手法
总结: - virtual 函数的替代方案包括 NVI 手法及 Strategy 设计模式的多种形式
- 将机能从成员函数移到 class 外部函数,带来的一个缺点是,非成员函数无法访问 class 的 non-public 成员
- trl::function 对象的行为就想一般函数指针,这样的对象可接纳 “ target signature兼容 ” 的所有可调用物
不重新定义继承而来的non-virtual函数
- base class 需满足适用于 base class 对象的每一件事也适用于 derived class 对象
- 所以不要重新定义继承来的 non-virtual 函数
不重定义继承而来的缺省参数值
- 不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而 virtual 函数却是动态绑定,解决方式采用 NVI 方式。
通过复合塑模出 has-a 或 “ 根据某物实现 ”(is-implemented-in-terms-of)
- 在应用域,复合(composition)意为 has-a 。在实现域(implementation domain),复合意为 is-implemented-in-terms-of(根据某物实现)
- 复合:
明智而审慎的使用private继承
如果是 private 继承关系,
- 编译器不会自动将一个 derived class 对象转换为一个 base class 对象
- 由 private base class 继承而来的所有成员,即 base class 中 protected 或 public,在 derived class 中都会变成 private 属性
- private 继承意味着 implemented-in-terms-of(根据某物实现),只有实现部分被继承,接口部分被略去
其他:
- 尽可能使用复合,必要时才使用 private 继承
- EBO(empty base optimization 空白基类最优化),一般只在单一继承才行
sizeof(HoldAnInt) == sizeof(int) 成立
总结:
- Private 继承以为 is-implemented-in-terms-of(根据某物实现)。当 derived class 需要访问 protected base class 的成员,或需要重新定义继承而来的 virtual 函数时
- Private 继承可以造成 empty base 最优化
明智而审慎地使用多重继承
- 使用 virtual 继承比 non-virtual 继承体积大,访问 base classes 成员变量时,virtual 也比 non-virtual 速度慢
- 非必要不使用 virtual base
- 使用 virtual base classes 尽可能避免在其中放置数据
总结:
- 多重继承比单一继承复杂,可能导致新的歧义性,以及对 virtual 继承的需要
- virtual 继承会增加大小、速度、初始化(及赋值)复杂度等成本。如果 virtual base classes 不带任何数据,将是最具实用价值的情况
- 多重继承的确有正当用途。其中一个情节设计 “ public 继承某个 Interface class ” 和 “ private 继承某个协助实现的 class ” 的两相组合
七、模板与泛型编程
了解隐式接口和编译期多态
声明(declaration)
C/C++ 中使用声明时,仅仅是告诉编译器某个东西的类型和名称,不分配实际的内存空间,不提供存储的位置和具体实现的细节。
定义(definition)
C/C++ 中使用定义时,编译器要给变量分配实际的内存空间,提供存储的位置和具体实现的细节。
签名(signature)
C++ 官方定义中,签名就是函数的参数部分,不包括函数的返回类型。但习惯上一般把函数返回类型和参数都作为签名的部分。
例如函数声明中 std::size_t func(int num);,func 函数签名为 std::size_t 和 int
- template 参数身上的隐式接口跟 class 对象的显式接口都是在编译期完成检查的,即无法在 template 中使用 “ 不支持 template 所要求的隐式接口 ” 的对象(编译不通过)
- classes 和 templates 都支持接口和多态
- 对 classes 而言接口是显式,以函数签名为中心。多态则是通过 virtual 函数发生于运行期
- 对 template 参数而言,接口是隐式的,奠基于有效表达式。多态则是通过 template 具现化和函数重载解析,发生于编译期
了解 typename 双重意义
- 当我们声明 template 类型参数时,class 和 typename 的意义完全相同
- template 内出现的名称如果相依于某个 template 参数(从属关系),如果从属名称在 class 内呈嵌套状(嵌套从属名称)
- 当你想要在 template 中指明一个嵌套从属类型名称,就必须在它前一个位置放上关键字 typename
- typename 不可以出现在 base classes list 内的嵌套从属类型名称前,也不能在 member initialization list(成员初值列)中作为 base class 修饰符
学习处理模板化基类内的名称
在 derived class templates 要指明 base class templates 内的成员名称时,会因为编译器不进入 base class 作用域内查找,可用以下解决:
- this->
- using 声明
- 明白指出调用函数位于 base class 内
将与参数无关的代码抽离 templates
- Templates 生成多个 classes 和多个函数,所以恩和 template 代码都不该与某个造成膨胀的 template 参数产生相依关系
- 因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或 class 成员变量替换 template 参数
- 因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码(如:vector,deque,list 等)
运用成员函数模板接受所有兼容类型
- 如果你声明 member templates 用于 “ 泛化 copy 构造 ” 或 “ 泛化 assignment 操作 ”,还需要声明正常的 copy 构造函数和 copy assignment 操作符
需要类型转换时请为模板定义非成员函数
- template 实参推到过程中不将隐式类型转换纳入考虑
- 通过 template class 内的 friend 声明式进行混合式调用,让编译器可在调用时使用隐式转换
使用 traits classes 表现类型信息
- 只有 random access(随机访问)迭代器才支持 += 操作,其余是反复进行 ++ 或 – 操作 n 次
设计 traits class:
- 确认需要取得的类型相关信息
- 为该信息选择一个名称,如 iterator_category
- 提供一个 template 和一组特化版本,内含类型相关信息
总结:
- Traits classes 使得 “ 类型相关信息 ” 在编译期可用,以 templates 和 “ templates 特化 ” 完成实现
- 整合重载技术后,traits classes 有可能在编译期对类型执行 if…else… 测试
认识 template 元编程
TMP(模板元编程)用途:
- 确保量度单位正确
- 优化矩阵运算
- 可以生成客户定制的设计模式实现品
总结:
- TMP 可将工作由运行期转移至编译期,得以实现早期错误侦测和更高的执行效率
- TMP 可被用来生成 “ 基于政策选择组合 ” 的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码
八、定制 new 和 delete
了解 new-handler 的行为
new-handler:
- 让更多内存可被使用,程序一开始执行就分配一大块内存
- 安装另一个 new-handler,当目前的 new-handler 无法取得更多可用内存时,可安装另一个 new-handler 以替换
- 卸除 new-handler
- 抛出 bad_alloc 异常
- 不返回,通常调用 abort 或 exit
总结:
- set_new_handler 允许指定一个函数,在内存分配无法获得满足时被调用
- Nothrow new 是一个颇为局限的工具,因为它只适用于内存分配,后继的构造函数调用还是可能抛出异常
了解 new 和 delete 合理替换时机
为什么要重写 new 和 delete:
- 检测运用上的错误,如 new 的内存被 delete 掉导致内存泄漏
- 强化效能,编译器所带的 new 和 delete 可能会导致内存分散,而导致无法满足大区块内存需求
- 收集使用上动态分配内存的统计数据
- 增加分配和归还的速度
- 降低缺省内存管理器带来的空间额外开销
- 你不缺省分配器中的非最佳补齐
- 将相关对象成簇集中
- 获得非传统行为
编写 new 和 delete 时需固守常规
- operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果无法满足内存需求,就该调用 new-handler。也应该有能力处理 0 bytes 申请,class 专属版本则还应该处理 “ 比正确大小更大的(错误)申请 ”
- operator delete 应该在收到 null 指针时不做任何事。Class 专属版本则还应该处理 “ 比正确大小更大的(错误)申请 ”
new 与 delete 成对出现
- 如果写了 operator new 而没有写对应的 operator delete 可能会发生隐微而时断时续的内存泄漏
- 声明 new 和 delete 时,不要遮掩了它们原本的正常版本
九、杂项讨论
不要轻忽编译器的警告
- 严肃对待编译器发出的警告信息
- 不要过度以来编译器的报警能力,因为不同的编译器并不相同
熟悉 TR1 在内的标准程序库
C++98:
- STL
- Iostreams
- Unicode 国际化支持
- 数值处理,复数模板、纯数值数组
- 异常阶层体系
- C89 标准程序库
TR1:
- 智能指针
- tr1::function
- hash-based
- 正则表达式
- tr1::bind
- …
TR1本身只是一份规范,实物来源 Boost
熟悉 boost
https://www.boost/
本文标签: Effective
版权声明:本文标题:【Effective C++】总结 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1726480693a1072529.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论