Effective C++ 一些记录和思考
Effective C++
- Iter 3 - 尽可能使用
const
一个反逻辑的 bitwise const
class Text {
...
char& operator[](std::size_t pos) const { return text[pos]; }
private:
char *text;
};
. 在 clang 3.8 上编译失败,编译器已经修复这个反逻辑的问题。const 成员函数只能返回 const char& 类型的变量,这就保证了对象不能被修改。
当存在 const 成员函数和 non-const 成员函数的时候,可先实现 const 成员函数,non-const 成员函数通过调用 const 函数来实现,具体做法为
先将对象转换为const类型(static_cast<const T&>()),调用const成员函数,再去除const属性(const_cast<T&>())
// const
const char& operator[](std::size_t pos) const { return text[pos]; }
// non-const
char& operator[](std::size_t pos) {
return const_cast<char&>(static_cast<const char&>(*this)[pos]);
}
. 这样做的好处是 const 成员函数保证了数据的不变,减少代码量,缺点是转换的性能缺失。
- Item 4 - 确定对象使用前已先被初始化
- 勿混淆赋值和初始化
- 对象的成员变量的初始化动作发生在进入构造函数
{/* body /*}
之前,使用member initialization list
初始化对象是一种比较好的做法 - 内置type(int char)默认为 0
- 初始化顺序为其声明次序
non-local static
编译器对不同编译单元的 non-local static 的编译顺序是随机的- 解决的办法是不直接访问 non-local static 对象,而是使用一个函数包装在其内部声明为 local static 对象并且返回改对象引用
- static 为声明在其作用域内的全局变量,比如在函数内,只要该函数栈没有被回收该变量便一直存在。在调用该函数时,该 local static 对象会在首次访问时被初始化
- Item 5 6 - C++ 隐式实现和调用的函数
- 编译器在没有 默认构造函数,copy构造函数,赋值重载操作符 时会自动生成,且为 public
- 阻止调用 copy构造函数,赋值重载操作符函数
- C++11 之前,实现一个基类然后继承
class UnCopyable {
public:
UnCopyable() {}
~UnCopyable() {}
private:
UnCopyable(const UnCopyable&);
UnCopyable& operator=(const UnCopyable&);
}; class Impl : private UnCopyable {};
- C++11 使用
delete
关键字
class Impl {
public:
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
};
- Item 7 - 为多态基类声明
virtual
析构函数- C++ 11 中子类的可以用
override
来覆盖基类的virtual
函数(析构函数可不用) - 由于virtual table 和virtual table pointer的存在,会使的class 的大小膨胀。
- 尽量不继承non-virtual 析构函数的类,C++11后可以用
final
关键字修饰类而禁止被继承 - 构造函数和析构函数执行方式相反,构造函数是从最顶层的基类开始执行
- C++ 11 中子类的可以用
- Item 8 - 别让异常逃离析构函数
- Item 9 - 不在构造和析构函数内调用
virtual
函数- 在多态基类中构造函数调用虚函数,子类对象构造时执行的基类构造函数调用的基类的对象,而非子类对象,析构函数相同,这样虚函数就变成普通的函数了
- Item 10 - 令
operator=
返回一个 reference to *this - Item 11 - 在
operator=
中处理“自我赋值”- 只看正确性,一般的做法是先保留一个副本,再进行赋值,最后删除副本,这样在赋值操作失败的时候,不会丢失原来的数据
- Item 12 - 复制对象时勿忘其每一个部分
- 一个较容易忽略的地方,子类在copy构造的时候易忽略对基类对象变量进行copy,而这时默认调用了基类的default构造函数
- 不要在一个构造函数内调用另外一个构造函数,可行的做法是将共同的机能放进一个普通函数中,在两个构造函数内调用
- Item 13 - 以对象管理资源
Resource Acquistion Is Initialization, RAII
auto_ptr
在标准中已经废除,用unique_ptr
替代shared_ptr
互相引用的问题,可以用weak_ptr
解决
- Item 14 - 在资源管理类中注意copying行为
- Item 15 - 在资源管理类中提供对原始资源的访问
- 在只能指针中提供原始指针即可
- Item 16 - 成对使用
new
和delete
- 尽量使用只能指针替代
new
- 数组可用
std::vector
或者 C++11 中的std::array
代替
- 尽量使用只能指针替代
- Item 17 - 以独立语句将
new
对象置入指针指针- 分离创建和使用的过程
- Item 18 - 让接口易于使用,不易被误用
- 类的设计通常应与内置类型的逻辑保持一致
- 函数的有资源相关的操作时可以考虑使用智能指针来处理
- Item 19 - 设计
class
如同 type - Item 20 - 宁以 pass-by-reference-to-const 替换 pass-by-value
- 涉及到底层的处理,编译器对待指针和自定义类型(class)的处理可能会不一样
- 对于内置类型、STL的迭代器和函数对象,pass-by-value 更合适
- Item 21 - 必须返回对象时,勿返回reference
- clang 3.8 可以发现这个问题,并发出警告
warning: reference to stack memory associated with local variable 'a' returned [-Wreturn-stack-address]
- clang 3.8 可以发现这个问题,并发出警告
- Item 22 - 将成员变量声明为
private
(能够访问private
的函数只有friend
函数和成员函数)- 将成员变量隐藏在函数接口后,可以为实现提供弹性
- Item 23 - 宁以 non-member、non-friend 替换 member 函数
- 将 non-member 函数与当前 class 声明在同一个命名空间内
- Item 24 - 若所有参数皆需类型转换,请采用 non-member 函数
- 只有当参数位于参数列内,这个参数才是隐式类型转换的合格者
- Item 25 - 考虑写出一个不抛出异常的 swap 函数
- 全特化版本,与
STL
保持一致性,这样 Widget 对象就可以正常的调用 swap 了
class Widget {
public:
void swap(Widget& other) {
using std::swap;
swap(pImpl, other.pImpl);
}
private:
WidgetImpl *pImpl;
};
namespace std { // std
template <>
void swap<Widget> (Widget& a, Widget& b) { a.swap(b); }
}
- 偏特化一个 function template 时,惯用手法是简单的为它添加一个重载版本
namespace WidgetFpp { // 这里不是添加到 std 中了
class WidgetImpl {};
class Widget {};
template<typename T>
void swap(Widget<T>& a, Widget<T>& b) { a.swap(b); }
}
- 一般经验
- 能使用 std::swap 就不去造轮子
- 有
pimpl
手法的,或类似的- 提供一个 public swap,置换该类型的两个对象
- 在当前 class 或者 template 所在的命名空间内提供一个 non-member swap,并调用上述成员函数
- 如果当前是 class 而不是 class template,为class特化
std::swap
,并调用 swap 成员函数
- 在调用 swap 前,使用
using std::swap
, 自动匹配合适的那个 swap 函数
- 全特化版本,与
实现
- Item 26 - 尽可能延后变量定义是的出现时间
- 以“具明显意义之初值”将变量初始化,还可以附带说明变量的目的
- Item 27 - 尽量少做转型动作
- 书中对 Window::OnResize() 的分析,这里用过代码看结果,L18 这里转型后调用的OnResize()的*this是一个副本,L19 直接调用基类 OnResize能够得到正确的结果
7 class Window {
8 public:
9 virtual void OnResize() {
10 a = 3;
11 }
12 int a;
13 };
14
15 class SpWindow : public Window {
16 public:
17 void OnResize() override { // C++ 11
18 // static_cast<Window>(*this).OnResize(); // 这里 a = 0
19 Window::OnResize(); // a = 3
20 }
21 };
- 谨慎使用
dynamic_cast
- Item 28 - 避免返回handles指向对象内部成分
- 书中的例子会造成 bitwise constness, 见 Item 3, 但是 clang 3.8 上这些代码已经不能编译通过了
- Item 29 - 值得为“异常安全”花费精力
- 异常安全的要求
- 不泄露任何资源
- 不允许数据败坏
- 资源泄漏用对象管理,如锁
- 单个函数的安全保证,
copy-and-swap
- 多个函数“连带效应”, 对每一个函数都实现安全保证
- 异常安全的要求
- Item 30 - 了解
inline
inline
和template
通常被定义于头文件内。 是因为通常而言,inlining 和 template 是编译期的行为,编译器必须知道被调用函数的本体- 改变程序需要重新编译,而不能像普通的函数直接链接即可
- Item 31 - 将文件间的编译关系降至最低
- Handle class 和 Interface class 解除接口和实现之间的耦合关系,降低文件间的编译依赖
- Item 32 - 避免遮掩继承而来的名称
- derived class 继承了 base class 内的所有东西,实际上的运行方式是 derived class 作用域被嵌套在 base class 作用域内
继承与面向对象设计
Item 33 - 确认
public
继承塑造出is-a
关系public
继承适用于 base class 对象上的每件事情也能够作用于 derived class 对象上- base class 和 derived class 中有相同的函数名称时,base class 中的函数就被覆盖了,
virtual
函数也是这样,但是有运行时多态,但是普通函数当想继承的时候就出问题了 - 在 derived class 加入
using base::func;
后相同参数和返回还是会覆盖 base class 中的对象
Item 34 - 区分接口继承和实现继承
- C++ 的隐式规则太多,需要继承的东西干脆显示化,减少出错
Item 35 - 考虑
virtual
函数以及以外的选择non-virtual interface, NVI
手法也是将真正需要的改变的部分分离出来放进一个函数内,而虚函数内先处理前置条件,再调用该函数
7 class Game {
8 public:
9 int health() const {
10 std::cout << "Game::health()\n";
11 int ret = doHealth();
12 std::cout << "Game:: ret " << ret << '\n';
13 return ret;
14 }
15 private:
16 virtual int doHealth() const {
17 std::cout << "Game:: doHealth()\n";
18 return 1;
19 }
20 };
21
22 class LOL : public Game {
23 private:
24 virtual int doHealth() const {
25 std::cout << "LOL doHealth()\n";
26 return 2;
27 }
28 };
29
30 int main() {
31 LOL lol;
32 lol.health();
33 }
// 可以使用到 base class 默认的析构函数,参考 Item39,用 private 作种实现方式。
// LOL 未定义虚函数的时候,结果为
// Game::health()
// Game:: doHealth()
// Game:: ret 1 // 定义虚函数后
// Game::health()
// LOL doHealth()
// Game:: ret 2
strategy
策略使用函数指针替换虚函数,这样每个对象都可以更灵活的有自己的特定处理函数,和运行时可以改变函数
7 class GameCharacter;
8 class HealthCalcFunc {
9 public:
10 virtual int calc(const GameCharacter&) const {
11 return 3;
12 }
13 };
14
15 HealthCalcFunc defaultHealthFunc;
16
17 class GameCharacter {
18 public:
19 explicit GameCharacter(HealthCalcFunc* phcf = defaultHealthFunc) : pHealthCalc(phcf) {}
20 int healthValue() const {
21 return pHealthCalc->calc(*this);
22 }
23
24 private:
25 HealthCalcFunc* pHealthCalc;
26 };
27
28 int main() {
29 GameCharacter gc;
30 gc.healthValue();
31 }
Item 36 - 绝对不重新定义继承而来的
non-virtual
函数- 继承而来的
non-virtual
函数是静态绑定,父类指针只能表现出父类对象的行为, 换而言之,这个在编译期就确定好了
- 继承而来的
Item 37 - 绝不重新定义继承而来的缺省参数值
- 缺省参数值都是静态绑定的,应该覆盖的是
virtual
函数,而它为动态绑定的
struct B {
virtual void mf(int i = 1) { std::cout << "B::mf " << i << "\n"; }
// 不改变 virtual 函数的时候,可以使用 NVI 手法,实现一个外围函数,令子类调用它
};
struct D : public B {
void mf() { std::cout << "D::mf\n"; }
};
int main() {
D d;
B *pb = &d;
pb->mf(); // B::mf
}
- 缺省参数值都是静态绑定的,应该覆盖的是
Item 38 - 通过复合
(composition)
塑造或根据某物实现出has-a
- 在应用域,复合意味着
has-a
,在实现域复合意味着 is-implemented-in-terms-of
- 在应用域,复合意味着
Item 39 - 谨慎使用
private
继承private
继承作为一种实现技术,意味着 is-implemented-in-terms-of, base class 为实现细节- 当两个class不存在
is-a
关系时,其中一个需要访问另一个的protected
成员,或者需要 重新定义 其一或者多个vitual
函数,可以考虑private
继承
Item 40 - 谨慎使用多重继承
- 多重继承易造成歧义(ambiguity)
- 干脆直接禁用了
模板与泛型编程
- Item 41 - 了解隐式接口和编译期多态
- Item 42 - 了解
typename
的双重意义- 声明
template
参数时,typename
和class
没有区别 - 利用
typename
标示嵌套从属类型名称(形如 C::const_iterator),但不得在 base class lists 或者 member initialization list 以他作为 base class 标示
- 声明
- Item 43 - 学习处理模板化基类内的名称
- 模板的具现是在函数使用的时候
- 编译器遇到 class template 继承的类时,并不知道该类的定义是什么
- 所以只能通过
this
指针指向 base class 对象,具现base class - 在该定义域内声明需要访问的 base class 函数
- 直接调用base class 中的代码
struct CompanyA {
void sendClear(const std::string& msg) {}
}; struct CompanyB {
void sendClear(const std::string& msg) {}
}; struct MsgInfo {}; template <typename Company>
class MsgSend {
public:
void send(const MsgInfo& info) {
std::string msg = "foo";
Company c;
c.sendClear(msg);
}
}; template <typename Company>
class LogMsg : public MsgSend<Company> {
public:
// using MsgSend<Company>::send; // slove - way 2: tell complier, send() is defined in base class
void sendMsg(const MsgInfo& info) {
// send(info); // base: cant pass complier
// this->send(info); // slove - way 1: could pass complier
// MsgSend<Company>::send(info); // slove - way 3: same as way 2
}
}; int main() {
MsgInfo mi;
LogMsg<CompanyA> ma;
ma.send(mi);
}
- 所以只能通过
- Item 44 - 将与参数无关的代码抽离
template
- 模板只有在使用时被具现,不同类型的会被具现出不同的代码,引起代码膨胀,有点儿类似宏的用法
- 或者参数使用相同的二进制表述,如指针
- Item 45 - 运用成员函数模板接受所有兼容类型
- 模板具现化后的 base class 和 derived class 为两个独立的类了
member function template
成员函数模板可接受所有兼容类型参数,也就是泛化了构造函数- 为阻止编译器默认生成copy构造函数和重载=,在已经有泛化版本的情况下也须自己定义这些函数
- Item 46 - 需要类型装换时须为模板定义非成员函数
- 模板函数在进行实参类型推导的时候,不允许进行参数的隐式转换(隐式转换是发生在函数调用的时候),而类型推导的时候是先根据已经有的定义确定函数原型
- 为了转换可以使用
friend
,感觉变复杂了
- Item 47 - 使用
traits classes
表现类型信息- 整合重载技术,使得traits class 有可能在编译期对类型执行if...else测试
- 使用 traits class
- 实现一组重载函数,或者函数模板,作为实现部分
- 建立一组驱动(控制)函数或者函数模板,调用以上重载函数
- Item 48 - 认识模板元编程(编写模板程序在编译期执行的过程)
一些新的知识点
- pimpl, 以指针指向一个对象,内含真正数据
- 模板全特化、偏特化的一个博客 https://blog.csdn.net/m_buddy/article/details/72973207
- copy and swap, 在副本上做修改,直到修改成功时再进行写入,在陈硕的muduo书里面讲到的,用swap在临界区外释放资源
Effective C++ 一些记录和思考的更多相关文章
- 初码-Azure系列-迁移PHP应用至Azure的一些实践记录和思考
最近客户在逐步迁移应用从阿里云到Azure,这次又轮到一个PHP+MySQL应用了,顺便也记一下流水账. 需求:迁移部署在阿里云上的ECS服务器(系列2,IO优化+2核4G+50G的SSD云盘+10M ...
- Effective C++学习记录
Effective C++算是看完了,但是并没有完全理解,也做不到记住所有,在此记录下55个条款及条款末的"请记住". 让自己习惯C++ 条款01:视C++为一个语言联邦 ① C ...
- 关于MYSQL 和INNODB的逻辑关系图。最好的理解是一点点动手做,观察,记录,思考。
每隔0.1秒就刷一次MYSQL文件的变化,并闪动标示出来,以观察SQL执行时,MYSQL的处理顺序. watch -n 0.1 -d stat /var/lib/mysql/ib_logfile0 / ...
- 读micro8的一些记录与思考
最近做了一段时间的攻击,个人对于整个攻击链相对来说还是比较熟悉.看了侯师傅的文章还是学到一些,做个备忘. 1.
- jmeter 4.0版本更新说明(个人做个记录)总版本更新合集
版本4.0 摘要 新的和值得注意的 不兼容的变化 Bug修复 改进 非功能性变化 已知问题和解决方法 谢谢 新的和值得注意的 核心改进 JMeter现在支持JAVA 9. 提供新的边界提取器元件,提供 ...
- 12月15日 session:Ruby on Rails Security Guide//从第3节开始没有学习//关于find_by 和where的区别用法思考。
http://guides.rubyonrails.org/security.html#user-management 2.session笔记见13日的随笔. http://www.cnblogs.c ...
- Effective C++学习笔记(1)
最近刚看完Effective C++,记录一下当前几个比较常用的方法. 1.以独立语句将newed对象置入智能指针 智能指针是以对象管理资源,在构造函数中获得资源并在析构函数中释放资源 以下调用: ...
- 不得不吐槽的Android PopupWindow的几个痛点(实现带箭头的上下文菜单遇到的坑)
说到PopupWindow,我个人感觉是又爱又恨,没有深入使用之前总觉得这个东西应该很简单,很好用,但是真正使用PopupWindow实现一些效果的时候总会遇到一些问题,但是即便是人家的api有问题, ...
- LR12.53—第5课:创建负载测试场景
在前面的课程中,您使用VuGen将验证您的Vuser脚本.在本课中,您将评估多个Vuser的负载下您的系统.您将模拟十个旅行代理同时使用航班预订系统的行动,以及这些用户的负载下观察系统的行为.设计和运 ...
随机推荐
- 10.6 Graph Test
一套图论的练习题,各个方面都有挺好的 第一第二题有一定难度(来源POI),第三第四题比较水 但我并没考好 T1 特工 szp T2 洞穴 zaw T3 最短路 line T4 最小差异值 dvalue
- 转 source insight 复制后光标在前面
source insight 里编辑的时候,每次粘贴后,光标停留在粘贴内容的前面. 我想把它设定为 粘贴后,光标移动倒粘贴内容的后面. 怎么做? 这是个设置问题,按照下面的步骤设定就可以了. Opti ...
- go语言---传值和传引用
go语言---传值和传引用 https://blog.csdn.net/cyk2396/article/details/78893828 1.定义: b = a; b.modify(); 如果b的修改 ...
- SYSUCPC2017 online round La La string 应用manacher算法
manacher算法给出一个字符串中 以每个位置为对称中心的回文串长度,但是大部分时候我们只需要知道以每个位置为起点的回文串长度,感觉有点浪费. 那么来看看这个不难也不太简单的题目 第一步,我们要想办 ...
- Spring MVC的学习笔记
基于注解形式配置Spring MVC 一.注册并初始化DispatcherServlet,由Servlet容器自动检测并启动 注解形式 public class MyWebApplicationIni ...
- A brief preview of the new features introduced by OpenGL 3.3 and 4.0
A brief preview of the new features introduced by OpenGL 3.3 and 4.0 The Khronos Group continues t ...
- Rabbitmq笔记一
几个基本概念 Producer 生产者,发送消息的一方,图中左侧的client. Consumer 消费者,接收消息的一方,图中后侧的client. Broker 消息中间件的服务节点,一般一个Rab ...
- 洛谷2019 3月月赛 T4
T3做不来.. 直接滚去T4 orz 乍一看 T4是个DP 题干 复杂度??(N^4) 咋优化... 还带一只捆绑 捆绑啥的最烦人了 最后20pts 直接废了 T了 很烦 不过拿到80pts已经很开心 ...
- datatable-bootstrap 基本配置
function doSearch() { if(dtable!=null){ dtable.fnClearTable(0); dtable.fnDraw(); // 重新加载数据 }else{ dt ...
- 【杂谈】HTML5到底给了我们什么?迟到的2016年终总结
其实提笔的时候日期已经到了3月了,不过由于在过去的2016年笔者发生了蛮多的事情,所以还是决定记录一下,那些关于成长的片段. 其实HTML5是在2012年的时候接触的,当时和结果志趣相投的同事,看到了 ...