Effective C++学习笔记
Posted Karthus_冲冲冲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Effective C++学习笔记相关的知识,希望对你有一定的参考价值。
目录
- 条款18. 让接口更容易被正确使用
- 条款19. 设计class犹如设计type
- 条款20. 宁以pass-by-reference-to-const替换pass-by-value
- 条款21. 必须返回对象时,别妄想返回其reference
- 条款22. 将成员变量声明为private
- 条款23. 宁以non-member、non-friend替换member函数
- 条款24. 若所有参数皆需类型转换,请为此采用non-member函数
- 条款25. 考虑写出一个不抛出异常的swap函数
条款18. 让接口更容易被正确使用
-
- 好的接口容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
避免接口误用的措施:
(1)引入新类型可以预防接口被误用。如果客户误用,则编译器被提醒类型不匹配:
(2)以函数代替对象。例如月份只有12个月,用枚举变量不安全,因此可以用函数代替对象:
(3)const修饰:限制类型什么能做,什么不能做。
(4)消除客户的资源管理责任:为了防止客户忘记使用智能指针而造成内存泄漏,接口应该提供在创建资源后,强制返回一个指向该资源的智能指针
,先发制人。
- 好的接口容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
-
- 尽量让你的types的行为与内置types的行为一致,相兼容。
-
- 智能指针shared_ptr支持自定制删除器,可以有效防范动态链接库DLL问题(对象在一个DLL中被new,却在另一个DLL内被delete)
条款19. 设计class犹如设计type
-
- class设计就是type设计。定义一个新的type之前,请考虑:新type的创建与销毁、初始化、赋值操作、值传递、合法值、继承体系(virtual)、类型转换、需要重载的操作符等等。
条款20. 宁以pass-by-reference-to-const替换pass-by-value
-
- 尽量以传const引用代替传值,传引用更加高效,并且可避免切割问题。
(1)by value需要创建新对象,可能需要调用多次构造和析构,而by reference没有任何新对象被创建。
(2)切割问题:当一个派生类对象以by value方式传递并被视为一个基类对象,那么基类的copy构造函数将会被调用,而原对象派生类的新性质被完全切割掉了,仅剩一个基类对象。这也会影响多态性质,错误调用重载的虚函数。而传引用不会发生以上情况。
- 尽量以传const引用代替传值,传引用更加高效,并且可避免切割问题。
-
- 以上规则并不适用于内置类型以及STL的迭代器和函数。它们更适合值传递。 因为本质上,const引用底层传递的是指针。
条款21. 必须返回对象时,别妄想返回其reference
-
- 绝不要返回指向一个局部栈对象的指针或引用:因为函数栈变量会在函数执行完毕后自动销毁,这时指向局部栈对象的指针或引用将会指向被销毁的内存,行为无定义。
-
- 绝不要返回指向一个堆分配的对象的引用:例如对返回引用的堆对象进行连续运算,连续运算会产生的中间无名对象。我们无法找到其指针为其进行析构,容易发生内存泄漏。
- 绝不要返回指向一个堆分配的对象的引用:例如对返回引用的堆对象进行连续运算,连续运算会产生的中间无名对象。我们无法找到其指针为其进行析构,容易发生内存泄漏。
-
- 绝不要返回指向一个局部静态对象的指针或引用:它们可能同时需要多个这样的对象,但事实只有一个静态对象(如下例ab的结果与cd的结果均是返回的同一个局部静态对象,if条件判断始终为真):
- 绝不要返回指向一个局部静态对象的指针或引用:它们可能同时需要多个这样的对象,但事实只有一个静态对象(如下例ab的结果与cd的结果均是返回的同一个局部静态对象,if条件判断始终为真):
-
- 上述例子必须返回新对象的函数正确写法:
- 上述例子必须返回新对象的函数正确写法:
条款22. 将成员变量声明为private
-
- 切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性:
(1)将成员变量设为private,使用函数可以实现对成员变量的处理精确控制。如果成员变量设为public,客户可以轻松读写成员变量,不安全且破坏封装性。此外,如果某一天你提供给客户的代码需要更新,而客户代码中有大量部分直接用到了成员变量,客户得到你的新代码库后需要修改大量的地方,并且重新编译,十分麻烦。如果用函数访问代替,客户将只用调用函数接口,不直接访问变量。只要函数接口不变,对象内部的设计变化弹性将会更大,有利于后续扩展新的功能,并且客户将不用为此修改代码、大量重新编译等等。
- 切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性:
-
- protected并不比public更具有封装性:假如我们有一个protected变量,而最终取消了它,那么使用它的派生类的代码都会被破坏。从封装的角度来说,只有两种访问权限:private(提供封装)和其他(不提供封装)。
条款23. 宁以non-member、non-friend替换member函数
-
- 宁以non-member、non-friend替换member函数。这样做可以增加封装性、包裹弹性和机能扩充性:
(1)越多东西被封装,我们在接口背后改变这些东西的能力就越大。
(2)导致较大封装性的是non-member non-friend函数,因为它并不增加“能够访问class内private成分”的函数数量。
(3)C++中,比较自然的做法是将一个非成员函数和类定义放在同一个namespace(命名空间)里。namespace可以跨越多个源码文件,可以轻松扩展在不同的文件中定义用于该class的非成员便利函数,只需用相同的命名空间就行。
- 宁以non-member、non-friend替换member函数。这样做可以增加封装性、包裹弹性和机能扩充性:
条款24. 若所有参数皆需类型转换,请为此采用non-member函数
-
- 令class支持隐式类型转换通常是个糟糕的主意,当然也有例外。最常见的例外就是建立数值类型(需要用到隐式类型转换)。
-
- 若成员函数参数需要隐式类型转换,混合运算可能会报错:只有当参数被列于参数列内,这个参数才是可以隐式转换的。下例中,oneHalf2可以是因为编译器自动识别出了传递的整型2(位于参数列内所以才识别出),但函数需要一个Rational对象,因此自动调用Rational的构造函数(必须是non-explicit,允许隐式转换)用整型2构造一个Rational对象。而2oneHalf报错是因为这个整型2并不位于Rational的操作符*函数参数列内,无法进行隐式转换。
- 若成员函数参数需要隐式类型转换,混合运算可能会报错:只有当参数被列于参数列内,这个参数才是可以隐式转换的。下例中,oneHalf2可以是因为编译器自动识别出了传递的整型2(位于参数列内所以才识别出),但函数需要一个Rational对象,因此自动调用Rational的构造函数(必须是non-explicit,允许隐式转换)用整型2构造一个Rational对象。而2oneHalf报错是因为这个整型2并不位于Rational的操作符*函数参数列内,无法进行隐式转换。
-
- 当operator * 成为一个非成员函数(能避免friend就避免friend)时,便允许编译器在每个参数上执行隐式转换:
- 当operator * 成为一个非成员函数(能避免friend就避免friend)时,便允许编译器在每个参数上执行隐式转换:
条款25. 考虑写出一个不抛出异常的swap函数
-
- 标准库中缺省版的swap函数无非就是a复制到tmp,b复制到a,tmp复制到b。然而当对象体积过于庞大时,反复复制会影响执行效率。可以设计一个这种体积庞大类的管理类,然后在管理类中用一个成员变量指针指向这个庞大类,swap就可以只交换指针即可。该方法被称为“pimpl手法”(pointer to implementation),这时通常需要自定义swap函数,同时要避免其抛出异常,因为swap的一个最好应用就是帮助class提供强烈的异常安全性保障。高效的swap函数几乎总是基于内置类型的操作(例如指针),而内置类型上的操作绝不会抛出异常
-
- 当缺省swap效率不足时,请尝试做以下事情:
(1)提供一个public swap成员函数,让它高效地置换你的类型的两个对象值;
(2)在你的class或template所在的命名空间内提供一个non-member swap,并令它调用上述swap成员函数;
(3)如果你正编写一个class (而非class template),为你的class特化std::swap。并令它调用你的swap成员函数。
最后,如果你调用swap,请确定包含一个using声明式,以便让 std: :swap在你的函数内曝光可见,然后不加任何namespace修饰符,赤裸裸地调用swap。
- 当缺省swap效率不足时,请尝试做以下事情:
-
- @注意@:为用户自定义类型进行全特化std的templates,但是不可以添加新的templates到std里。
《Effective C++ 》学习笔记——条款11
***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
二、Constructors,Destructors and Assignment Operators
Rule 11:Handle assignment to self in operator =
规则11:在 operator= 中处理“自我赋值”
1.自我赋值?!
比方这种:
class Widget { ... }; Widget w; ... w = w; // 赋值给自己
这样做是同意的,所以不要期盼不会发生,由于鸟大了,什么林子都有 o(╯□╰)o。。
2.自我赋值 其它形式
① 除了最直观的 w=w
② 另一些隐蔽的,比方:
a[i] = a[j]; // 万一 i等于j
③ *px = *py
也许。这两个指针都指向同一个对象
④ 还有 base class 与 derived class的:(由于一个基类的引用或者指针。能够指向派生类对象)
class Base { ... }; class Derived : public Base { ... }; void doSomething( const Base& rb , Derived* pd);<span style="white-space:pre"> </span>// rb 和 pd 可能指向同一个对象
3.愚蠢的自我赋值,会导致?
class Bitmap { ... }; class Widget { ... private: Bitmap* pb; }; Widget& Widget::operator=( const Widget& rhs )<span style="white-space:pre"> </span>// 一分不安全的 operator= 实现版本号 { delete pb;<span style="white-space:pre"> </span>// 停止使用当前的 Bitmap pb = new Bitmap(*rhs.pb);<span style="white-space:pre"> </span>// 使用 rhs's的Bitmap 副本 return *this;<span style="white-space:pre"> </span>// 这个问题在上一个条款(条款10)中介绍过 }
这个样例说明了虾米呢?
就是,假设是
a = b;
工作原理是。先把a的版本号删除。然后在将b赋值给a。
所以,假设a与b 是同一个东西,它就会导致错误,
按上面的样例来讲,就是,删除pb的同一时候。rhs也被删除了,所以第二行的赋值动作就会指向一个已经被删除的对象。
4.看到错误了,总归要解决的
有三种解决方式
<1> 第一种方案——“证同測试” identity test
Widget& Widget::operator=(const Widget& rhs ) { if( this == &rhs ) return *this;<span style="white-space:pre"> </span>// identity test delete pb; pb = new Bitmap(*rhs.pb); return *this; }就是在运行代码前。先推断是否是同一个东西,若是,就不须要做不论什么事情。直接返回。
<2> 另外一种方案——先赋值,再删除
Widget& Widget::operator=(const Widget& rhs) { Bitmap* pOrig = pb;<span style="white-space:pre"> </span>// 先备份 pb = new Bitmap(*rhs.pb);<span style="white-space:pre"> </span>// 赋值 delete pOrig;<span style="white-space:pre"> </span>// 删除备份 return *this; }就是仅仅须要注意在复制之前别删除原来的,先备份一下。
这也许不是处理“自我赋值”最高效的办法。但它行得通。
<3> 第三种方案——copy and swap
class Widget { ... void swap( Widget& rhs );<span style="white-space:pre"> </span>// 交换*this和rhs的数据,在后面条款29会有具体解释 ... }; Widget& Widget::operator=( const Widget& rhs ) { Widget temp(rhs);<span style="white-space:pre"> </span>// 为rhs数据制作一份复件 swap(temp); return *this; }
并且,还有还有一种形式。对于:①某class的 copy assignment操作符声明为“以 by value 方式接受实參”。②以by value方式 传递东西会造成一份复件:
Widget& Widget::operator= ( Widget rhs )<span style="white-space:pre"> </span>// rhs是被传递对象的一份复件 {<span style="white-space:pre"> </span> swap(rhs);<span style="white-space:pre"> </span>// 注意这里是 pass by value return *this;<span style="white-space:pre"> </span>// 将*this的数据和复件的数据互换 }
而这样的做法也是作者比較不推荐的。由于它为了 伶俐巧妙的修补 牺牲了 代码的清晰性。可读性略低。
但 将 copying 动作 从函数本体 移至 函数參数构造阶段 却能够 让编译器产生更高效的代码。
5.请记住
★ 确保当对象自我赋值时 operator= 有良好行为。当中技术包含比較“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
★ 确定不论什么函数假设操作一个以上的对象,而当中多个对象是同一个对象时,其行为仍然正确。
***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
以上是关于Effective C++学习笔记的主要内容,如果未能解决你的问题,请参考以下文章