Effective C++ 总结

本文详细介绍了C++编程中的关键原则与细节,包括使用const、enum、inline替代#define,确保对象初始化,利用const提高安全性与效率,合理使用const成员函数与迭代器,以及如何避免异常逃离析构函数等最佳实践。文章还覆盖了C++中资源管理、设计与声明、实现、继承与面向对象设计等多个方面,旨在帮助开发者提升代码质量与性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.尽量以const,enum,inline代替#define
reason:你所使用的名称可能并未进入记号表。
可以用const double AspectRation=1.635替换#define ASPECT_RATION 1.635


定义常量的char *based字符串如下:
const char * const authorName="Scott Meyers";
在C++中使用string比较合适:
const std::string authorName("Scott Meyers");




const可以成为类专属常量
class CostEstimate
{
private:
   static const double FudgeFactor; //常量声明
};
const double CostEstimate::FudgeFactor=1.35;//常量定义,初值初始化可以放在常量声明处。


如果你不想让别人获取一个指针或引用指向你的某个整数常量,enum可以帮助你实现这个约束。


template<typename T>inline void callWithMax(const T& a,const T& b)
{
f(a>b?a:b);
}
a.对于形似函数的宏,最好改用inline函数替换#define
b.对于单纯常量,最好以const对象或enums替换#define


2.尽可能的使用const


char greeting[]="Hello";
char *p=greeting; //non-const pointer,non-const data;
const char * p=greeting;//non-const pointer,const data;
char * const p=greeting;//const pointer,non-const data;
const char * const p=greeting; //const pointer,const data;


void f1(const Widget *pw);//f1获取一个指针,指向一个不变的Widget对象。f2也是,两者写法一样。
void f2(Widget const *pw);


STL迭代器的作用就像是T *指针,声明迭代器为const就像声明指针为const一样(T *const指针),表示迭代器不能指向不同的东西,但他所指的东西的值可以改变。const T *相当于const_iterator;


const可以让函数返回一个常量值,可以降低因客户错误而造成的意外,而不至于放弃安全性和高效性。


const Rational operation*(cont Rational &lhs,const Ration &rhs);
如果不这样做,客户可能会写成(a*b)=c;,加上const可以预防没有意思的赋值动作。


const成员函数:使class接口比较容易理解,使“操作cosnt对象”称为可能。


两个成员函数如果只是常量性的不同,可以被重载。
const char& operator[](std::size_t position)const     //操作const对象
{
return text[position];
}
char & operator[](std::size_t position) //操作non-const对象
{
return text[position];
}




TextBlock tb("Hello");
cosnt TextBlock ctb("World");
std::cout<<tb[0];
tb[0]='x'; //返回值要是char的引用,要不然就不能赋值
std::cout<<ctb[0];
ctb[0]='x';//错误,写一个const对象。






const成员函数不能修改non-static的成员变量,如果要修改,要在声明成员变量前加mutable关键字。


运用const成员函数实现其non-const函数需要运用const_cast和static_cast转换,这样可以避免代码重复。
calss TextBlock
{
public:
const char &operator[](std:size_t position)const
{
...
...
return text[position];
}


char &operator[](std::size_t position) //调用const版本
{
reutrn const_cast<char &)(static_cast<const TextBlock &>(*this)[position]);
}
};


static_const将non-const对象转换成const对象,const_cast从返回值中移除const.




3.确定对象被使用前已被初始化


对于内置类型以外的东西,初始化的责任在构造函数上。




初始化与赋值是不同的,赋值最好用下面的方式写:
ABEntry::ABEntry(string &name,string &address):theName(name),theAddress(address)
{
//什么也不做
}
与放在函数体里面的最终结果一样,但是上面的方法效率高。


4.了解C++默认编写并调用哪些函数


如果写下:
class Empty{};
编译器会生成默认的构造函数,copy构造函数,赋值操作符
class Empty
{
 public:
   Empty(){}   //默认的构造函数
   Empty(const Empty &rhs){}
   ~Empty(){}
  Empty & operator=(const Empty &rhs){} 
}


如果一个类含有引用成员,必须自己定义copy assignment操作符。




5.若不想使用编译器自动生成的函数,就应该明确拒绝




class HomeForSale
{
public:
 private:
 HomeForSale(const HomeForSale&); //不实现
 HomeForSale& operator=(const HomeForSale&);//防止编译器自动生成copy构造函数和赋值操作符,这样防止用户调用


}


6.为多态基类声明virtual析构函数。


不加virtual可能导致局部销毁对象。任何的类只要带有virtual函数都几乎要一个virtual虚函数。


可以将析构函数定义为纯虚析构函数,但是要为纯虚析构函数提供一份定义,因为析构函数是先调用最深层派生的那个类的析构函数,然后是每一个基类的析构函数。
所以必须为纯虚析构函数一个定义。
class AWOV
{
public:
virtual ~AWOV()=0;
}
AWOV::~AWOV(){} //提供定义
将析构函数定义为虚函数只针对要提供多态性的基类定义。




7.别让异常逃离析构函数
析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常。


8.绝不在构造函数和析构函数中调用virtual函数




9.令operator=返回一个reference to *this


int x,y,z;
x=y=z=15;
为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参。
class Widget
{
public:
Widget& operator=(const Widget& rhs)
{
return *this; //指向当前对象


}
}


10.在operator=中处理“自我赋值”


自我赋值检验:
class Bitmap{};
class Widget
{
private:
Bitmap *pb; //指针,指向一个从heap分配而得到的对象
}
Widget& Widget::operator=(const Widget &rhs)
{
if(this==&rhs) return *this;  //如果是自我赋值,就不做任何事


delete pb;
pb=new Bitmap(*rhs.pb);
return *this;
}
或者用下面:
Widget& Widget::operator=(const Widget &rhs)
{
Bitmap *pOrig=pb;  //记住原先的pb
pb=new Bitmap(*rhs.pb); //令pb指向*pb的一个复件
delete pOrig;  //删除原来的pb
return *this;
}




11.在继承当中,子类复制对象时别忘记每一个成分即父类


如:
class Date{};
class Customer
{
public:
private:
std::sgring name;
Date lastTransaction;
};


class PriorityCustomer:public Customer
{
public:
PriorityCustomer(const PriorityCustomer &rhs);
PriorityCustomer & operator=(const PriorityCustomer& rhs);
private:
int priority;


};


PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):Customer(rhs),priority(rhs.priority)      //调用基类的复制构造函数
{
logCall("PriorityCustomer copy constructor");
}


PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy  assignment operator");
Customer::operator=(rhs); //对基类成分进行赋值动作
prority=rhs.priority;
return *this;
}




-----------------------------------------资源管理--------------------------------------


12.以对象管理资源
class Investment{};
Investment *createInvestment(); //工厂方法


void f()
{
Investment *pInv=createInvestment();


...
delete pInv;//释放pInv所指的对象,但是这个存在潜在的资源泄露的可能性
}


可以使用auto_ptr来避免
void f()
{
std::auto_ptr<Investment> pInv(createInvestment());
//由auto_ptr的析构函数自动删除pInv
}
对于动态分配的数组不要使用该方法。




13.成对使用new和delete时要采取相同的形式
不要对数组形式做typedef动作
std::string * ptr1=new std::string;
std:string *ptr2=new std::string[100];
delete ptr1;
delete [] ptr2;//删除是由对象组成的数组




14.以独立的语句将newed对象放入只能指针
processWidget(std::auto_ptr<Widget>(new Widget),priority());
上面应该分开写:
a. std::auto_ptr<Widget> pw(new Widget);
b.processWidget(pw,priority());




-----------------------------------------设计与声明--------------------------------------
15.用const的引用传递参数去替代按值传参数
这样可以更高效,可以避免切割问题,该规则不适合内置类型以及STL的迭代器和函数对象。


16.当函数必须返回对象时,不要返回其引用


const Rational & operator*(const Rational &lhs,const Rational &rhs)
{
Rational *result=new Rational(lhs.n*rhs.n,lhs.d*rhs.d);//on-the-heap
return *result;
}
//上面的写法无法完成delete调用,导致资源泄露
“必须返回对象”的正确写法:
inline const Rational  operator*(const Rational &lhs,const Rational &rhs)
{
 return  Rational(lhs.n*rhs.n,lhs.d*rhs.d);  


}
绝对不要返回pointer或reference指向一个local stack对象,或返回指向一个heap-allocated对象或返回pointer或local static对象而有可能同时需要多个这样的对象。




17.将成员变量声明为private,protected并不比public更具有封装性。


18.若所有的参数皆需类型转换,请采用非成员函数


例如运算符*的重载:
class Ration
{
public:
const Rational operator*(const Rational &rhs)const;




};


混合式算术:result=oneHalf*2;//正确
              result=2*oneHalf;//错误,不能隐式转换


应该采用非成员函数
class Rational{....};


const Rational operator*(const Rational &lhs,const Rational&rhs)  //若不是采用Rational的成员函数,需要将这个函数声明为Rational的friend...
{
return Rational(lhs.n*rhs.n,lhs.d*rhs.d);
}
Rational oneFourth(1,4);
Rational result;
result=oneFourth*2;
result=2*oneFourth; //现在两个都可以
如果需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个非成员函数。






----------------------------------------- 实现--------------------------------------
19.尽量少做转型


C++提供的新式转型
 const_cast<T>(expression)  //通常用来将对象的常量性转除
dynamic_cast<T>(expression)  //用来决定某对象是否归属继承体系中的某个类型
reinterpret_cast<T>(expression)
static_cast<T>(expression) //强迫隐式转换,例如将non-const对象转换为const对象


20.避免返回handles指向对象内部成分


class Rectangle
{
public:
  const Point& upperLeft() const{return pData->ulhc;}  //第一个const不能丢掉,要不然客户就可以修改Point内部数据,破坏封装性
}


21.将文件的编译依存关系降至最低
尽量以class的声明式替换class的定义式










----------------------------------------- 继承与面向对象设计-------------------------------------
22.确定你的public继承塑模出is-a的关系


23.避免遮掩继承而来的名称


class Base
{
private:
int x;
pubilc:
virtual void mf1()=0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
};


class Derived:public Base
{
public:
virtual void mf1();
void mf3();
void mf4();
};


调用:Derived d;
int x;
d.mf1(); //正确,调用Derived::mf1();
d.mf1(x);//错误,因为Derived:mf1遮掩了Base:mf1
d.mf2();//正确,调用Base::mf2();
d.mf3();//正确,调用Derived::mf3();
d.mf3(x);//错误,因为Derived::mf3遮掩了Base::mf3


为了避免上面的遮掩,可以采用如下方式:


class Derived::public Base
{
public:
using Base::mf1; //让Base Class 内名为mf1和mf3的所有东西在Derived作用域内可见(并且public)
using Base::mf3;
virtual void mf1();
void mf3();
void mf4();
};




若不想继承Base 的所有函数,可以采用private继承
class Base
{
 public:
  virtual void mf1()=0;
virtual void mf1(int);
};
class Derived:private Base
{
public:
  virtual void mf1()
{
Base::mf1();
}
};


调用:
Derived d;
int x;
d.mf1();//调用Derived:mf1
d.mf1(x);//错误,Base::mf1()被遮掩了


24.区分接口继承和实现继承


a.声明一个纯虚函数的目的是为了让子类只继承函数接口。
b.声明一个虚函数的目的是让子类继承该函数的接口和缺省实现。
c.声明一个非虚函数的目的是为了让子类继承函数的接口及一份强制性实现。
25.绝不重新定义继承而来的非虚函数


26.绝不重新定义继承而来的缺省参数值


class Shape
{
public:
  enum ShapeColor{Red,Green,Blue};
void draw(ShapeColor color=Red)const
{
doDraw(color); //调用一个虚函数
}
private:
 virtual void doDraw(ShapeColor color) const=0;
};


class Rectangle:public Shape
{
public:
  private:
  virtual void doDraw(Shape color)const; //不需要指定缺省参数值,已继承
};




27.通过复合塑模出has-a的关系
这样可以保证子类的的默认参数会一致,因为缺省参数值是静态绑定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值