《effective c++》第一遍简记

1.将c++视为一个语言组
次语言 (1)c (2)Object-Oriented C++ (3)Template C++ (4)STL
c++高效编程守则视状况而变化,取决于你使用c++的哪一部分
2.尽量以const,enum,inline替换#define
#define定义的记号在编译器处理源码之前就被预处理器移走了,可能并未进入symbol table
例   #define ASPECT_RATIO 1.653
   改为 const double AspectRatio = 1.653;
使用常量可能比#define有更小的码量,#define会导致多个1.653,const常量就不会
const替换#define时的特殊情况:
(1)写两次const保证指针和指向的内容均不可更改
const char* const authorName = "liupeng";
string往往比char更合适些
const std::string authorName("liupeng");
(2)class的专属常量,确保常量最多一份,加static
class face{
static const int Myeyenum = 2;//将其在class中声明之后,若不取它的地址,无须提供定义式,把这个定义式放在实现文件里
}
  已经赋初值了,可以定义如下:
const int face::Myeyenum;                                                                     类内static必须要是常量才能赋初值,即加上const

在编译器不允许的情况下可以使用enum来初始化常量,如下
class GamePlayer { 
enum {Myeyenum = 2};
……
};
       enum类型不允许获得地址,类似#define
                                  
#define 实现宏函数时,将每个参数加上小括号,但仍然会有错误发生
此时可以使用template inline来替换
template<typename T>
inline void manage T(const T& a, const T& b)
  {
a>b?a:b;
}

#ifdef/#ifndef在定义头文件时使用
请记住
单纯常量,最好以const和enum替换#define
形似函数的宏,使用inline替代#define
3.尽可能使用const
const出现在*号左边,表示被指的是常量,出现在*号右边表示指针是常量。
用于STL迭代器时
const std::vector<int>::iterator iter = vec.begin();//表示指针不能变
std::vector<int>::const iterator iter = vec.begin();//表示指向内容不能变


加与不加const(最后)的函数算重载,可以实现接受相同参数,返回值不同的两个函数

利用mutable可以修改const的约束,被mutable标记的member不受const的限定,可以在函数中修改这些member

转型 cast操作
请记住:
const与non-const有实质等价实现时,令non-cosnt调用const可避免重复代码,即在返回值加上转除操作
4.确定对象被使用前以初始化:
若在初始化列表中需要初始化的数据太多,可以把一些内置变量移到函数内用赋值完成,一般放在一个函数中(private),供所有构造函数调用。
最好初始化列表次序与声明次序相同

c++对于"定义于不同编译单元内的non-local staic对象"的初始化次序无明确要求,为了消除未初始化就使用的隐患,将每个non-local static 对象搬到自己的专属函数(对象在函数内被声明为static),函数返回reference指向object。即用local static代替non-local static。如下:
Myclass tfs(){
static Myclass mc;
return mc;
}
一个函数内就做了返回一个local Myclass的工作,直接用tfs().成员即可。


任何一种static对象,在多线程等待下都会有麻烦,处理麻烦的一种做法:单线程启动阶段,手动调用reference-return函数,可以消除与初始有关的race conditions。


请记住:
内置型对象手工初始化
构造函数用member initialization list,排列次序与class中声明次序相同,不要在ctor中使用assignment。
使用上述方法以local static替换non-local static对象。
5.了解c++默认编写了哪些调用函数
当有成员是string*时,必须自己编写复制构造函数。
6.若不使用编译器自动生成的函数,则明确拒绝
每个object仅有一次的情况下,想拒绝构造函数和copy ctor,需要将其copy与copy assignment操作符声明为private且不定义。
另一种可行的办法是实现专门阻止copy的class:
class Uncopyable{
protected:
Uncopyable();
~Uncopyable();
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
接下来仅需要private继承即可:
class Myclass:private Uncopyable{……};
7.为多态基类声明virtual析构函数
基类指针指向derived class时,同过base point 删除 derived class时,若base class的dtor不是virtual,未调用derived dtor,会造成delete掉了base部分,而derived部分还在。
正确的做法是将base class的dtor声明为virtual。


若需要一个纯虚函数作为基类,可以声明一个pure virtual的析构函数,并未这个纯虚dtor加上定义(纯虚函数是可以有定义的,一般为空,无定义析构的时候无法调用)。


请记住:
多态的base class应将dtor声明为virtal
若不是作为base class继承用,则不需要声明为virtual函数。
8.别让异常逃离析构函数
c++并不鼓励让dtor吐出异常。一个class vector,若在第一个元素的dtor吐出异常,其他九个还应被销毁,若第二个还吐出,那么程序很可能就中止了。
若close抛出异常就结束程序,用abort:
DBConn::~DBConn()
{
try{
db.close();
}
catch(……){
std::abort();
}
}
还可以吞下因调用close引发的异常
DBConn::~DBConn()
{
try{
db.close();
}
catch{...}{
记录下close调用失败的信息;
}
}
因此修改后的函数为:
class DBConn{
public:
...
void close()
{
db.close();
closed = true;
}//这个close是给客户,让他决定是否调用close
~DBConn()
{
if(!closed){
try{
db.close();
}
catch(...){
记录信息
}
}
}//若此处出现问题,客户没理由抱怨,毕竟他曾经可以第一手处理问题。
private:
DBConnection db;
bool closed;
}
 
请记住:
析构函数绝对不要吐出异常,若抛出异常,则应吞下它们或结束程序。
若客户需要对某func运行期间的异常做出反应,则class应提供一个函数(非dtor)执行该操作
9.不在dtor和ctor中调用virtual函数
构造是多态不生效,若生效,base class的point调用derived的virtual时,derived的data member还未初始化成功,得到的结果是未定义的。析构时同样,多态生效,derived已经析构了,引起未知操作。****与此同时,base的ctor调用base的virtual,derived调用derived的virtual function。****

为了在ctor中调用正确的函数,一种:把base中的virtual改为non-virtual,然后由derived class传递必要的信息给base的ctor,例子如下:
class baseclass{
public:
explicit baseclass(string &loginfo);
void func(string &loginfo);
};
baseclass::baseclass(string &loginfo)
{
func(loginfo);
}
class derivedclass:public baseclass{
public:
derivedclass(parameters):baseclass(func(parameters)){...}
private:
static std::string func(parameters);
};
如上,derivedclass中的static func是作为辅助函数(根据需要看是否为static),通过parameters计算传给baseclass的值。
10.令operator= 返回一个reference to *this
适合连续赋值
11.在operator中处理“自我赋值”
常见的处理自我赋值的方法:
在operator= 最前加上证同测试:
if(this == &classobject)
return *this;


处理异常安全往往就处理掉了自我赋值问题:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
如上,new时异常,pb与rhs中的pb仍不变,而且能够处理自我赋值问题。


替代方案copy and swap技术(不推荐):
Widget& Widget::operator=( Widget rhs)
{
swap(rhs);
return *this;
}
参数传入一个副本,再交换数据。但有些情况不适合(如有data member为指针时)
12.复制对象时别遗漏成分
copy和copy assignment不能相互调用,若二者有重复代码,那就建立一个新的member function给二者调用,往往是private的,叫做init。
*记住:
确保复制所有data member和base class成分。 
13.以对象管理资源:
将资源放进对象里,确保被释放。auto_ptr就是一个类指针对象。
void (){
std::auto_ptr<classtype> pclass(createobject());
}
调用了factory函数产生object,auto_ptr的析构函数自动删除pclass。
思路:(1)获得资源后立刻放进管理对象
(2)管理对象用析构函数保证资源释放。
为防止多个指向同一个,在赋值或复制时,复制所得的指针将取得唯一的资源拥有权,原来的变为null。

shared_ptr,会跟踪有几个对象指向这个资源,若没有,则删除(在函数结束变量消失,会自动删除资源),它的复制行为比auto_ptr正常多了。
***auto_ptr和shared_ptr在其析构中做delete而不是delete[],因此,分配数组指针给它俩是不行的。
14.在资源管理类中小心coping行为
若一个RAII对象被复制,可以:(1)禁止复制,将copy声明为private
(2)对底层资源使用引用计数,内含一各std::trl::shared_ptr
(3)复制底部资源,即深度复制
(4)转移底部资源拥有权
*记住:
复制RAII时一并复制它所管理的资源
常见:禁止copy、引用计数等。
为了避免忘掉释放,建立一个类来管理,析构函数里写释放
15.在资源管理类中提供对资源的访问
可以写一个转换函数,显示返回所需资源的指针
*记住:
APIs需要访问原始资源,需提供一个方法
对原始资源访问,显式转换比较安全,隐式较方便
16.成对使用new和delete时要采取的相同形式
delete需知道删除数组时的大小,在内存中有记录,加上[]后,它会去寻找。
17.以独立语句将newed object放入智能指针。
即在给智能指针赋值的()中不要添加其他函数,因为一旦其他函数异常,则资源泄露。
******设计与声明******
18.让接口容易被正确使用,不易被误用
在接口上使用自己定义的类,并在类的构造函数上用explicit声明(防止隐式类型转换)。
???
19.设计class如设计type
设计class往往面临以下问题:
(1)新type如何被创建和销毁
(2)对象的初始化和赋值有什么差别
(3)type被passed by value,会发生什么?
(4)type的“合法值”
(5)type需要配合某个继承图系嘛?
……
20.以pass by reference to const 替换pass by value
以reference为函参时,不会出现derived class被切割的情况。
reference实现时由point完成的,所以对于内置类型,by value的效率要比by reference高些。
*注意:
以上并不适用于STL的迭代器和函数对象,pass by balue 往往比较适当
21.必须返回对象时,别妄想返回其reference
返回函数内的local object时用reference是错误的,函数结束会销毁object。可以new一个用来返回的reference。

若必须返回一个对象(比如operator *),那就返回一个对象。
22.将成员变量声明为private
只有声明为private,在以后修改代码的时候才能做最少的修改
23.宁以non-member、non-friend替换member函数
non-member 与non-friend function有更大的弹性
c++中自然的做法,将clearBrowser成为一个non-member函数并位于WebBrowser所在的同一个namespace内。
将不同用途的类和函数放到不同的头文件中,并用namespace封装。功能不同,但是一个类下的,放到不同头文件中用一个namespace包含。
24.若所有参数皆需类型转换,请为此采用non-member函数
如果是member函数,例如*的情况下,会有一种情况不能转换,因此改为non-member函数(参数要配合类型转换才能用)或public中的函数。
可以避免friend函数就避免
25.写一个不抛出异常的swap函数
swap是个模板函数,需要类型T支持copying。
对于member带指针的class,可以将swap特殊化并作为class的member function:
namespace std {
void swap< Class<T> > (Class<T>& a,Class<T>& b){
a.swap(b);
}
}
这样子不行,c++不允许对function进行特化,那么用模板重载,也不行,std不允许添加新的templates到std里头。
因此声明一个non-member swap让他调用member swap,如下:
namespace std{
template<typename>
void swap(Widget<T>& a,Widget<T>& b)//这里是添加一个重载版本的swap
{ a.swap(b);}
}
提供一个public swap成员,让他高效置换。
在class或template所在命名空间提供一个non-member swap,并令它调用上述swap成员函数
为class特化std::swap,并令它调用swap函数。
若用到swap,请使用using声明,让std::swap对函数内部可见
记住:
std::swap对你的类型效率低时,提供一个swap函数,并确定这个函数不抛出异常
若提供一个member swap,也应提供给一个non-member swap(当然参数可能不同)来调用前者,对于classes,特化std::swap
千万不要在namespace std中加入全新东西
实现:
26.尽可能延后变量定义式的出现时间
避免因为没用到引起的构造操作,另外,尽量在定义时就给它赋初值,避免无意义的default构造调用。
在循环内部尽量避免定义类型,或许考虑赋值与构造+析构的代价再做决定更好。
27.尽量少做转型操作 casting
c++提供四种转型:
const_cast<T>(expression)//移除对象常量性,如const、volatile(确保本指令不会被优化)
dynamic_cast<T>(expression)//类的上下行转换,执行下行时由类型检查功能,比static_cast更安全,另外还支持交叉转换
reinterpret_cast<T>(expression)//安全性不高,可以用于多种转换
static_cast<T>(expression)//强迫隐式转换,类指针的上行转换
大范围的转型会导致很低的运行速度。


替代品有:1.将基类指针存到容器中,用于指向不同的derived class.
 2.利用virtual function,实现基类指针调用不同函数
记住:
如果可以,避免转型操作
转型是必要的,可以隐藏在某个函数后面
宁可用你c++z转型,不用旧式转型
28.避免返回handles执行对象的内部成分
references、point、迭代器都是handles,它们作为返回值时可能会被用来修改内部数据 ,破坏了封装。需要对返回类型加上const。
另外若指向内部成分,有可能对象被销毁了(作为返回值),指针就变成dangling point了。
oeprator[]允许返回handle
记住:
避免返回的handles指向对象内部。
29.为“异常安全”而努力是值得的
一个软件系统要么有异常安全,要么全盘否定
当异常被抛出是,带异常安全的函数会:
不泄露任何资源
不允许数据损坏
        *****将可能导致异常的语句尽可能地延后*****
异常安全函数提供三个保证之一:
若异常被抛出,程序内任何事物仍保持在有效状态下。
若异常被抛出,程序状态不改变
承诺绝不抛出异常

另外copy and swap是个不错的处理异常的方法
为打算修改的建一个副本,然后在副本上做必要的修改,若抛出异常,原对象不变,待修改后再做swap操作。

记住:
强力保证异常安全往往能够 用copy and swap 实现出来,但并非所有都有实现意义(如果占过多资源)。
函数异常安全性等于其内部调用函数异常安全性最低的那一个。
30.了解inline的里里外外
慎重使用inline,将它用在小型、频繁的函数上。
不要将function templates在头文件,就声明为inline,因为function templates往往要到运行期才能确定具像化为什么function
31.将头文件的编译依存关系降到最低
能用object references 或object pointers完成,就不使用object。
尽量以class声明式替换class定义式
记住:
依赖声明式而不是定义式(定义式即需要知道大小),手段:handle calsses和interface classes
程序库头文件应仅有声明式
32.确定public继承的is-a关系
公有继承。
33.避免遮掩继承来的名称
继承后virtual会覆盖原有的function
34.区分接口继承和实现继承
public继承分为:函数接口继承、函数实现
记住:
derived classes总是继承base class的接口
pure virtual 函数只具体指定接口继承
***简谱的impure virtual函数指定接口及缺省实现继承***
non-virtual函数具体制定接口继承及强制性实现继承
35.考虑virtual函数以外的其他选择**********
base class中virtual未声明为pure时,应该提供一个default function
***non-virtual interface实现template method模式:
在public中定义一个non-virtual函数,调用private中定义的virtual函数做实际的工作。
non-virtual叫做virtual的外覆器(wrapper)
wrapper在virtual function运行前后可以做一些准备、善后工作
***function pointers 实现的strategy模式
定义一个函数让ctor接受一个指针,指向需要的函数
(1)不同实体可以有不同的目标函数
(2)函数可在运行期变更
***由tr1::function完成Strategy模式
**function**
类模板,类的成员变量是一个函数指针
void foo(int i) {cout<<"jj01"<<endl;}
void (*p)(int) = foo;
int main(){
tr1::function<void (int)> fm;
fm = foo;
(*p)(2);
fm(2);
return 0;
}
它类似于函数指针,但有函数指针做不到的地方,
(1)定义一个function对象
(2)像函数指针一样,这个指针需要指向某个函数
(3)function重载了()符号,用着能好用些
较函指优点:
能够指向static、外部函数
类的非static成员函数(需指明是哪个对象的成员函数)
绑定虚函数,不改变多态
**         **
private中内含一个函数指针,指向实际操作的函数,可以兼容更多。
***古典的strategy模式***
在另一个继承体系中实现需调用的函数。
以上是替代virtual的几种方案,当然还有更多,根据实际需要设计。
记住:
将调用的函数从member function移到class外部,会导致无法访问non-public成员。
36.不重新定义继承而来的non-virtual函数
静态绑定的函数,要调用只和定义的类型有关,不同于多态的静态绑定。
37.不重定义继承而来的缺省参数值
non-virtual上一条已经说过
virtual的情况:virtual是动态绑定,缺省参数是静态绑定
· 即可能会引发由base point指向derived class时,调用了base class对应函数的参数,显然不对。(这里的描述可能需要理解后才能看懂)
38.符合塑模出has-a或根据某物实现出
复合与继承是不同的两个概念
39.谨慎使用private继承
private意味着实现部分被继承,接口部分被略去,是一种实现技术
private可以造成empty base最优化(极端情况)。
40.谨慎使用多重继承
virtual继承有额外的维护成本
???
41.了解隐式接口和编译器多态
template<typename T>
void doProcessing(T& w)
{
if(w.size()>10 && w != someNastyWidget){
T temp(w);
temp.normalize();
temp.swap(w);
}
}
如上:w必须支持哪种接口由template中执行的操作决定。
凡涉及w的任何函数调用,有可能造成template具现化function,不同参数,template的function不同,叫编译期多态。
显示接口由签名式构成,隐式接口(如上):(1)提供一个名为size的成员函数,该函数返回一个整数值。(2)必须支持一个operator!=函数来比较两个T函数  但事实是可能不需要,可以藉由隐式转换或继承来摆平,叫隐式接口。
记住:
templates多态通过template具现化和函数重载解析,发生在编译期
42.了解typename的双重意义:
在template中class与typename并不一直有相同的意义 
在template中设计一个嵌套丛书类型名称,就必须在它前加上typename告诉编译器,它是个名称,不是一个成员。但在base classes list与member initialization list中不可以修饰base class。
43.学习处理模板化基类内的名称
在继承模板类时,编译器并不知道derived class继承的是哪个函数,需要在具现化后才能确定。
使用this->调用函数
使用using声明式,表明调用的是那一个模板里的函数
using msgsender :public msgsender<company>::sendClear
记住:
模板类继承,之后调用函数,通过一个this->指涉base class templates内的成员,。
44.将与参数无关的代码抽离
non-template中重复的十分明确,而在template中,具现化多次可能发生重复。????????
记住:
templates生成多个classes和多个函数,所以任何template代码都不该 与某个造成膨胀的template参数产生相依关系
非类型模板参数造成的代码膨胀,往往以函数参数或class成员变量替换template参数。
因类型参数造成的代码膨胀,可以让有相同二进制表述的具现类型共享实现码
45.运用成员函数模板接受所有兼容类型
构造和赋值函数可以写为模板用来进行类型转换。
复制一个auto_ptr&时,它其实被改动了。
记住:
声明member template用于泛化copy和assignment,仍需要声明正常的copy和assignment
46.需要类型转换时,请为模板定义非成员函数。
记住:
编写一个class template,它提供所有参数的隐式类型转换,请将那些函数定义为class template内部的friend函数。
47.请使用traits classes表现类型信息
traits class使类型相关信息在编译器可用,以template和template特化实现。
重载可使traits classes在编译器对类型执行if else测试。
48.认识template元编程
TMP,执行与c++编译器内的程序,编译期会长,但文件,运行,内存可能会相应减少。
49.了解new-handler的行为
new内存不足时,抛出异常,但可以设置抛出异常时调用的函数,<new>中的set_new_handler用来设置回调函数,定义如下
namespace std{
typedef void(*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
使用:
void outofmem(){std::cout<<"memory lose"; std::abort();}
int main(){
std::set_new_handler(outofmem);

}
static成员必须在class中(除非他们是consnt)
    记住:
set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
50.了解new和delete的合理替换时机
 在new是可能会面临alignment(malloc就是在这个约束)的问题,
   何时在全局性与class专属的基础上合理替换缺省的new和delete:
为了检测错误
手机动态分配内存之使用统计信息
降低内存管理带来的空间开销
。。。
51.编写new和delete时需固守常规
??
记住:
operator new应内含一个无穷循环,在其中尝试分配内存,无法满足,调用new-handler。还能够处理0bytes的申请.
operator delete应在收到null后什么也不做
52.。。
54.熟悉tr1在内的标准程序库

55.熟悉boost

后记:这个需要过第二遍再整理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值