effectiveC++文档格式.docx
《effectiveC++文档格式.docx》由会员分享,可在线阅读,更多相关《effectiveC++文档格式.docx(29页珍藏版)》请在冰点文库上搜索。
![effectiveC++文档格式.docx](https://file1.bingdoc.com/fileroot1/2023-4/30/a11526ed-a67d-434e-a9a7-6022e7f2b22d/a11526ed-a67d-434e-a9a7-6022e7f2b22d1.gif)
2、集成一个专门用来防止copy构造和赋值操作
classUncopyable{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable&
operator=(constUncopyable&
Uncopyable(constUncopyable&
classA:
publicUncopyable{
NO7为多态基类声明virtual析构函数
1、
classperson{
person(){}
~person(){}
classstudent:
publicperson{
person*p=newstudent;
deletep;
此时只能删除person部分,而student剩余部分无法删除。
解决办法:
persion的析构函数设为virtual。
2、没有virtual函数的类不需要virtual析构函数,那样只会增加对象的大小。
3、当继承一个没有virtual的stl类时会产生类似于1中的情况
classMyString:
publicstring{}
MyString*pms=newMyString();
string*ps=pms;
deleteps;
//只能部分delete
NO8析构函数不要抛出异常
1、如果某个操作可能引起异常,且发生异常时必须要处理,则需将这个操作放在非析构函数中,以使得用户有时间处理他们。
NO9构造、析构函数不要调用virtual函数
构造函数和析构函数中不能调用virtual函数,因为此时构造尚未完成,所以不可能调用派生类类的方法而只能调用基类的方法。
NO10operator=返回*this
赋值可以写成连锁形式:
x=y=z=15;
因此operator=一般应该返回一个引用指向当前对象:
val(99){}
intval;
operator=(constA&
rhs){
this->
val=rhs.val;
return*this;
}
Aa,b,c;
c.val=(100);
a=b=c;
NO11在operator=中处理自我赋值
方法1:
if(this->
p==rhs.p)
classPeople{
classHouse{
House(People*_p):
p(_p){}
House&
operator=(constHouse&
if(this->
p==rhs.p){return*this;
deletep;
p=rhs.p;
People*p;
方法2:
House&
People*_p=this->
p;
p=newPeople(*rhs.p);
delete_p;
方法3:
自己写一个swap函数
NO12复制对象时需要复制每一个成分
People&
operator=(constPeople&
rhs){
age=rhs.age;
//this->
name=rhs.name;
如果遗忘了,编译器不会提醒
intage;
stringname;
classStudent:
publicPeople{
Student&
operator=(constStudent&
People:
:
operator=(rhs);
id=rhs.id;
intid;
NO13以对象管理资源
classDBConnection{
DBConnection*conn_pool(){
returnnewDBConnection;
voidfunc(){
DBConnection*conn=conn_pool();
//……如果发生异常,或者提前return则conn指向的对象无法删除
deleteconn;
2、解决办法:
voidtest(){
std:
auto_ptr<
DBConnection>
pdb(conn_pool());
pdb.get()->
do_sth();
3、注意事项
auto_ptr复制时,参数指针变成null(因为需要防止两个指针指向同一个对象)
不能作为容器元素
4、shared_ptr
可以追踪对象的引用计数,可以用来作为容器元素
5、auto_ptr和sharted_ptr析构函数内均做delete而不是delete[],因此它们不适合指向动态分配的array对象。
NO14shared_ptr删除器
classA{};
voiddel_func(A*a){
cout<
"
删¦
?
除y对?
象¨
®
A*a=newA;
shared_ptr<
A>
pa(a,del_func);
NO15在资源类中提供对原始资源的访问
1、显示转换,.get()->
*等等:
a(7){}
voiddisplay(){cout<
值¦
Ì
:
ê
o"
a<
inta;
pa(a);
typeid(pa.get()).name()<
pa->
display();
(*pa).a<
2、隐式转换
classDB{
DB():
name("
oracle"
){}
classDBManager{
DBManager(DB&
d):
data(d){}
operatorDB()const{
returndata;
DBget(){returndata;
private:
DBdata;
voidprint_db(DBd){
d.name<
DBd;
DBManagerdbm(d);
print_db(dbm.get());
//显示转换
print_db(dbm);
//通过重载运算符()的隐式转换
print_db(dbm.operatorDB());
//和上面一句是等价的
NO16delete与delete[]
1、删除数组指针要用delete[]
2、注意typedef导致的混淆
typedefstringAddressLines[4];
string*pal=newAddressLines;
//等价于string*pal=newstring[4];
delete[]pal
//但是如果没有看到typedf语句的话很容易把pal当成一个string的指针,从而写出了:
deletepal;
NO17以独立语句将new对象置入智能指针
classWidget{};
intget_priority(){
staticinti=0;
returni++;
voidprocessWidget(std:
tr1:
shared_ptr<
Widget>
pw,intpriority){
//dosomething;
//错误做法:
因为编译器并没有确定processWidget前后两个参数产生顺序,如果//get_priority()调用在newwidget和构造shared_ptr直接发送异常会导致内存泄漏。
//processWidget(std:
(newWidget),get_priority());
//正确做法:
把构造shared_ptr的语句放到函数调用外面。
pw(newWidget);
processWidget(pw,get_priority());
NO18让接口容易被正确使用
1、导入新类型以预防客户端错误
2、避免与内置类型不一致
eg:
重载operator*()需要加上修饰符const,以使得a*b=c不合法
3、使用shared_ptr,直接返回包装好的指针(且同时制定删除器而不是由用户对shared_ptr进行删除)
NO19略
NO20用const的引用传递代替值传递
1、值传递的函数使用的是传递过来对象的副本,所以函数不能能改变他,因此换做引用传递时必须保证是constXXX&
x.
2、引用传递还可以防止对象切割导致的问题:
virtualvoidspeak()const{cout<
iamapeople"
classChinese:
voidspeak()const{cout<
我是中国人"
//Chinesepfunc(p)只能输出"
因为根据实参构造形参时把baseclass
//以外的部分切割了。
voidfunc(Peoplep){
p.speak();
//可以正常运行
voidfunc(constPeople&
p){
当然了,通过这种方法是调用的必须是const成员函数(因为不管事值传递还是const引用传递都不可能改变原来的对象)。
3、pass-by-value成本不高的集中情况:
内置类型、STL的迭代器、函数对象
NO21必须返回对象时别妄想返回其引用
几种错误情况:
1、Studen&
get(){
Students(“name”,13);
returns;
这样返回的引用指向的student在函数返回时就已经被析构了
即使函数内设置staticStudent也有问题(多线程安全性、两次get()所得对象无法比较)
2、Student&
Student*s=newStudent(“name”,13);
这样删除Student对象变得很困难。
尤其是x*y*z这种隐藏的指针根本无法删除。
NO22将成员变量声明为private
1、提高封装性,改变实现后用户代码可以保存不变
2、其实protected和public一样会带来封装性的严重下降
NO23用非成员函数、非友元替换成员函数
1、no-member相比于member函数可以使更少的代码访问类的private成员
2、采用no-member函数,并将不同作用的non-member函数分布在不同的头文件中会有效提高扩展性:
NO24若参数均需类型转换,改用non-member函数
classRational{
Rational(intnumerator=0,intdenominator=1):
nu(numerator),de(denominator){}
intnumerator()const{returnnu;
intdenominator()const{returnde;
intnu;
intde;
若Rational中增加一个成员函数operator*:
constRationalopeartor(constRational&
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&
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<
vector>
usingstd:
vector;
namespacemy{
template<
classT>
classA{
public:
A(vector<
T>
*_p):
voidswap(A&
vector<
*tmp=rhs.p;
rhs.p=p;
this->
p=tmp;
}
vector<
*p;
int*ptr;
};
voidswap(A<
&
lhs,A<
lhs.swap(rhs);
NO26尽可能延后变量定义式的出现时间
1、延迟变量定义到使用前一刻,并且尽量直接构造而不是复制。
2、
//方法1成本:
1构造+1析构+n赋值
MyClassA;
for(inti=0;
i<
n;
++i){
A=…
//方法2成本:
n构造+n析构
MyClassA=…
NO27尽量少做转型动作
1、static_cast的一个错误例子:
B():
i(0){}
virtualvoidpp(){++i;
inti;
classD:
publicB{
voidpp(){
B:
pp();
//static_cast<
B>
(*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{
virtualvoidf1()=0;
virtualvoidf1(double){cout<
B:
f1(double)"
voidf2(){cout<
f2()"
voidf2(int){cout<
f2(int)"
publicB{
usingB:
f1;
f2;
virtualvoidf1(){cout<
D:
f1()"
Dd;
d.f1();
d.f1(1.1);
//需要加入语句usingB:
d.f2();
d.f2
(1);
//需要加入语句usingB:
2、对于这种遮蔽的情况加入using声明可以解决,但是用using会把改名字的所有方法引进来。
而private继承中有时只需要用到其中某些函数:
例如,Derive类可能只需要Base类中无参数的函数f2、有参数的f1,此时需要用到转交函数:
classD2:
privateB{
voidf1(doubled){
f1(d);
voidf2(){
f2();
此时D2的f1(double)、f2()可用而f1()、f2(int)不可用。
NO33区分接口继承和实现继承
1、纯虚函数purevirtual只提供接口
2、普通虚函数提供接口和默认实现
3、非虚函数提供接口和强制实现
PS:
普通虚函数的默认实现可能带来的问题:
由于疏忽,子类使用了默认实现,但是这个实现不符合子类的业务逻辑。
解决方法:
1、提供一个接口和一个protected实现函数。
但是会带来命名污染。
2、方法声明提供接口、方法定义提供默认实现:
classAirplane{
virtualvoidfly()=0;
voidAirplane:
fly(){cout<
默认实现"
//A型飞机适用于默认实现
classModelA:
publicAirplane{
voidfly(){
Airplane:
fly();
//B型飞机不适用与默认实现
classModelB:
//此时B必须要提供fly的实现,因此也就不会由于疏忽而错误
cout<
B的实现"
NO34用非virtual函数代替virtual函数的手法
1、NVI非虚函数接口调用虚函数实现
public:
voidspeak(){
doSpeak();
}
virtualvoiddoSpeak(){cou