};
//Chinesepfunc(p)只能输出"iamapeople",因为根据实参构造形参时把baseclass
//以外的部分切割了。
voidfunc(Peoplep){
p.speak();
}
//可以正常运行
voidfunc(constPeople&p){
p.speak();
}
当然了,通过这种方法是调用的必须是const成员函数(因为不管事值传递还是const引用传递都不可能改变原来的对象)。
3、pass-by-value成本不高的集中情况:
内置类型、STL的迭代器、函数对象
NO21必须返回对象时别妄想返回其引用
几种错误情况:
1、Studen&get(){
Students(“name”,13);
returns;
}
这样返回的引用指向的student在函数返回时就已经被析构了
即使函数内设置staticStudent也有问题(多线程安全性、两次get()所得对象无法比较)
2、Student&get(){
Student*s=newStudent(“name”,13);
returns;
}
这样删除Student对象变得很困难。
尤其是x*y*z这种隐藏的指针根本无法删除。
NO22将成员变量声明为private
1、提高封装性,改变实现后用户代码可以保存不变
2、其实protected和public一样会带来封装性的严重下降
NO23用非成员函数、非友元替换成员函数
1、no-member相比于member函数可以使更少的代码访问类的private成员
2、采用no-member函数,并将不同作用的non-member函数分布在不同的头文件中会有效提高扩展性:
NO24若参数均需类型转换,改用non-member函数
1、
classRational{
public:
Rational(intnumerator=0,intdenominator=1):
nu(numerator),de(denominator){}
intnumerator()const{returnnu;}
intdenominator()const{returnde;}
private:
intnu;
intde;
};
若Rational中增加一个成员函数operator*:
constRationalopeartor(constRational&rhs){
returnRational(nu*rhs.nu,de*rhs.de);
}
则:
Rationalr4=r*2;//正确
Rationalr5=2*r;//错误
这是因为:
只有当参数被列于参数列时,他才是隐式转换的合格参与者,因此r*2中的2被当成了operator*(constRational&)中的参数。
而2*r中的2不在参数列表中。
2、正确做法:
改成non-member函数:
constRationaloperator*(constRational&lhs,constRational&rhs){
Rationalr(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());
returnr;
}
此时2*r也可以正确运行
tips:
这个non-member函数不需要作为友元,因为他可以通过函数访问nu,de.
member函数的反面是non-member函数而不是friend函数。
NO25swap函数
有时swap函数效率很低(eg:
类A有一个指向向量的指针,而A的复制函数是需要将向量中的元素也复制。
此时swap函数会多次复制向量元素,而实际上只需要交互指针就行了)。
解决办法:
添加swap()成员函数,并提供class所在命名空间的swap非成员函数。
#include
usingstd:
:
vector;
namespacemy{
template
classA{
public:
A(vector*_p):
p(_p){}
voidswap(A&rhs){
vector*tmp=rhs.p;
rhs.p=p;
this->p=tmp;
}
private:
vector*p;
int*ptr;
};
template
voidswap(A&lhs,A&rhs){
lhs.swap(rhs);
}
}
NO26尽可能延后变量定义式的出现时间
1、延迟变量定义到使用前一刻,并且尽量直接构造而不是复制。
2、
//方法1成本:
1构造+1析构+n赋值
MyClassA;
for(inti=0;iA=…
}
//方法2成本:
n构造+n析构
for(inti=0;iMyClassA=…
}
NO27尽量少做转型动作
1、static_cast的一个错误例子:
public:
B():
i(0){}
virtualvoidpp(){++i;}
inti;
};
classD:
publicB{
public:
voidpp(){
B:
:
pp();
//static_cast(*this).pp();这样改变的是D的base部分的一个副本,所以最终i=1而不是2.
++i;
}
};
NO28避免返回指向对象内部成分的句柄
1、返回这样的句柄可能导致封装性下降
2、当句柄的寿命比所指对象长时会导致:
danglinghandles(因为对象被销毁时,句柄所指内容也不复存在,但是这个句柄被传到外界了,别人使用这个句柄时便指向了无效的内容。
)
NO29为异常安全而努力
1、异常安全的函数:
√不泄露资源(例如用Lock类代替lock()函数)
√不允许数据败坏
2、三个保证:
√基本承诺:
有效状态,但不知是哪种状态
√要么成功、要么完全失败。
√不抛异常
NO30inline函数
1、节省调用时间、增加代码大小(可能导致额外的换页)
2、只是一个申请,不是强制命令
3、virtual函数不可能是inline,因为执行前无法判断具体调用哪个函数,从而进行替换
4、函数指针进行的调用通常不会inline
inlinevoidfunc(){cout<<"dosomething"<void(*fptr)()=func;
func();//通常是inline的
fptr();//通常不会是inline
5、看似简单的构造函数、析构函数有时也不适合inline,因为构造、析构函数中会调用成员变量、基类的构造、析构函数,这些加起来可能相当庞大。
NO31将文件的编译依存关系降至最低
1、引用和指针只需要类型声明,而对象需要定义式
classNode;
classTree{
Node&root;//正确
Node*root;//正确
Noderoot;//需要:
#include"Node.h"
}
2、函数声明中用到对象,可以只提供声明
classPeople;
Peoplekiss(Peoplegf);//如果加上{…}变成函数定义就不合法了
好处:
namespacemyLib{
classData;
classVector;
classString;
//...
Datafunc1();
voidfunc2(Vectorv);
Stringappend(Stringstr);
}
这样使用func1()的用户只需要includeData.h,使用func2()的用户只需要includeVector.h……这样就是的用户不需要使用一个函数而引入所有其他函数需要的类。
3、使用句柄类和接口类能解除接口和实现之间的耦合关系、减少头文件的编译依赖关系(同时他们也能避免用户知道类的实现细节,增加安全性)。
NO32public继承是is-a关系
1、baseclass中重载基类方法会将基类中该方法,和该方法的所有重载方法不可见。
classB{
public:
virtualvoidf1()=0;
virtualvoidf1(double){cout<<"B:
:
f1(double)"<voidf2(){cout<<"B:
:
f2()"<voidf2(int){cout<<"B:
:
f2(int)"<};
classD:
publicB{
public:
usingB:
:
f1;
usingB:
:
f2;
virtualvoidf1(){cout<<"D:
:
f1()"<voidf2(){cout<<"D:
:
f2()"<};
Dd;
d.f1();
d.f1(1.1);//需要加入语句usingB:
:
f1;
d.f2();
d.f2
(1);//需要加入语句usingB:
:
f2;
2、对于这种遮蔽的情况加入using声明可以解决,但是用using会把改名字的所有方法引进来。
而private继承中有时只需要用到其中某些函数:
例如,Derive类可能只需要Base类中无参数的函数f2、有参数的f1,此时需要用到转交函数:
classD2:
privateB{
public:
voidf1(doubled){
B:
:
f1(d);
}
voidf2(){
B:
:
f2();
}
}
此时D2的f1(double)、f2()可用而f1()、f2(int)不可用。
NO33区分接口继承和实现继承
1、纯虚函数purevirtual只提供接口
2、普通虚函数提供接口和默认实现
3、非虚函数提供接口和强制实现
PS:
普通虚函数的默认实现可能带来的问题:
由于疏忽,子类使用了默认实现,但是这个实现不符合子类的业务逻辑。
解决方法:
1、提供一个接口和一个protected实现函数。
但是会带来命名污染。
2、方法声明提供接口、方法定义提供默认实现:
classAirplane{
public:
virtualvoidfly()=0;
};
voidAirplane:
:
fly(){cout<<"默认实现"<//A型飞机适用于默认实现
classModelA:
publicAirplane{
voidfly(){
Airplane:
:
fly();
}
};
//B型飞机不适用与默认实现
classModelB:
publicAirplane{
//此时B必须要提供fly的实现,因此也就不会由于疏忽而错误
voidfly(){
cout<<"B的实现"<}
};
NO34用非virtual函数代替virtual函数的手法
1、NVI非虚函数接口调用虚函数实现
classPeople{
public:
voidspeak(){
doSpeak();
}
private:
virtualvoiddoSpeak(){cou