您的位置:首页 > 编程语言 > C语言/C++

effective C++ 3rd 笔记(一)

2011-04-05 21:12 169 查看
条款01:视C++为一个语言联邦 : C, Object-Oriented C++, Template C++, STL

条款02:尽量以const,enum,inline 替换#define:

1.对于单纯常量,最好以const对象或enum替换#define

2.对于形似函数的宏(macros),最好改用inline函数替换#define。

#define ASPECT_RATIO 1.653

const double AspectRatio = 1.653;

1)用#define定义的名字可能没有进入记号表,编译出错,不方便追踪

2)用const定义常量将得到更精简的目标代码(object code),因为预处理器处理#define是会盲目的将宏名称替换为1.653导致目标码出现多份1.653若用const则不会出现这种问题。

当用常量替换#define有两种特殊的情况。

1)定义常量指针。由于常量定义式通常放在头文件内(以便被不同源码含入),因此有必要将指针声明为const。 const char* const authorName = “Scott Meyer”; string对象通常比char*-base合宜,所以authorName往往定义成这样:const std::string authorName(“Scott Meyer”); 2)class专属常量。为了将常量的作用域限制于class内,和确保此常量至多只有一份实体,必须让它成为一个static成员: class GamePlayer { private: static const int NumTurns = 5; int scores[NumTurns]; ... } 通常static & const& 整数类型(ints,chars,bools)类型成员如果不取地址,可以声明并使用而无需定义。但如果你取某个class专属常量的地址,或你的编译器却(不正确地)坚持要看到一个定义式就必须提供如下定义式 const int GamePlayer::NumTurns; //放到实现文件,因为在声明时已获得初值,不能再设初值。 我们无法用#define 创建class专属常量,因为#define不重视作用域scope。 旧时编译器也许不支持上述语法,不允许static成员在声明时提供初值。 则将初值放到定义式里,但编译器坚持必须在编译期间知道数组score[]大小,万一你的编译器不允许static 整数型class常量完成 in class初值设定,可改用 the enum hack补偿做法。 class GamePlayer{ private: enum{ NumTurns=5 }; int scores[NumTurns]; } 1)取enum和#define地址一样,不合法。如果你不想让别人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。enums和#defines一样不会导致非必要的内存分配。 2)实用,很多代码用了。 enum hack是 模板元编程template metaprogramming的基础技术。 另一个常见的#define误用情况是用它实现宏。宏看起来像函数,但不会招致函数调用带来的额外开销。 #define MAX(a,b) f((a)>(b)?(a):(b))
int a =5, b = 0;
MAX(++a, b);//a被累加二次
MAX(++a, b+10);//a被累加一次 替换为内联模板函数:宏的效率和类型安全,可预料行为
template<typename T>
inline void MAX(const T&a,const T&b)
{
f(a>b?a:b);
} 条款03: 尽可能实用const const Rational operator* (const Rational& lhs, const Rational& rhs);//为什么返回const对象? Rational a, b, c; … (a * b) = c;// if(a * b = c) … // 其实想做比较操作,返回const可预防此类问题 const成员函数:可被const对象调用。两个函数如果只是常量性不同,是可以被重载。 可用mutable释放掉non-static成员变量的bitwise constness约束(即mutable修饰的成员变量可被const成员函数修改)。 在const和non-const成员函数中避免重复: class TextBlock { public: … const char& operator[] (std::size_t position) const { … return text[position]; } char& operator[] (std::size_t position) { //non-const成员函数调用const成员函数避免代码重复 return const_cast<char&>( static_cast<const TextBlock&>(*this) [position]); } private: std::string text'; }; 不能令const成员函数调用non-const成员函数,对象有可能被改动。 条款04: 确定对象被使用前已先被初始化。 1.对内置类型手工初始化 2.构造函数最好使用成员初值列 member initialization list,而不要在构造函数体内用赋值操作。初值列中列出的成员变量次序要跟在class中声明次序一致。 3.为免除跨编译单元之初始化次序问题,用local static 对象替换non-local static 对象 class A { public: A(const string&); private: string str; int num; } A::A(const string &s){ str=s; //赋值而非初始化,在初值列中调用一次default构造函数,函数体内一次赋值操作 num=0'; //内置类型不保证。 } C++规定对象的成员变量初始化动作发生在进入构造函数体之前。如果成员变量是const或引用,则一定要初始化,而不能被赋值。 A::A(const string &s):str(s), //值调用一次copy构造函数 num(0){} static对象:从被构造到程序结束为止。main()结束时自动析构。 包括global对象,namespace内的对象,在class内、函数内、及在file作用域内被声明为static的对象。 local static对象:函数内的static对象 non-local static对象:其他 如果某个编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,则可能未被初始化。C++对”定义于不同便一单元内的non-local static对象“的初始化次序无明确定义。 class FileSystem { public: … std::size_t numDisks() const; }; extern FileSystem tfs; //另一编译单元 class Directory { public: Directory(params); }; Directory::Directory(params) { std::size_t disks = tfs.numDisks(); } Directory tempDir (params); 解决方案:singleton模式,用local static对象替换non-local static对象 C++保证,函数内的local static 对象会在该函数被调用期间,首次遇上该对象定义式时被初始化。 class FileSystem {…}; FileSystem& tfs() { static FileSystem fs; return fs;} class Directory {…}; Directory::Directory (params) { std::size_t disks = tfs().numDisks(); }; Directory& tempDir() { static Directory td; return td;} 条款05:了解C++默默编写并调用哪些函数 编译器暗自为class创建default构造函数,copy构造函数,copy assignment操作符,以及析构函数 只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。 如果没有定义复制构造函数,编译器就会合成一个,即使已经定义了其他的构造函数。 template<class T> class NamedObject { public: NamedObject(string &name, const T &value):nameValue(name),objectValue(value){} private: string &nameValue; const T objectValue; }; string newDog(“aaa”); string oldDod(“bbb”); NamedObject<int> p(newDog,2); NamedObject<int> s(oldDog, 36); p = s; //编译失败 在内含引用或const成员的class内支持赋值操作符,你必须自己定义copy assignm操作符。 如果某个base class将copy assignment操作符声明为private,编译器拒绝为其derived classes生成一个copy assignment操作符。 条款06: 若不想使用编译器自动生成的函数,就该明确拒绝。 方案:将相应成员函数声明为private并不予实现。或使用Uncopyable这样的base class 明确声明一个成员函数,阻止编译器暗自生成,另其为private,阻止外部调用。 但成员函数和friend仍然可以反问,所以只声明而不定义它们,如不慎调用,则会得到一个连接错误。 将连接期错误移到编译器: class Uncopyable{ protected: Uncopyable(){} //允许derived对象构造和析构 ~Uncopyablel(){} private: Uncopyable(const Uncopyable&); //但阻止copying Uncopyable& operator=(const Uncopyable&); }; class HomeForSale:private Uncopyable { …}; 当尝试拷贝HomeForSale对象时,自动生成copy构造函数和copy assignment操作符,并尝试调用基类对于版本,但失败。。。 条款07: 为多态基类声明virtual析构函数

1.若果class带有任何virtual函数,它就应该拥有一个virtual析构函数

2.class的设计目的不是作为base class使用或不是为了具备多态性,就不改声明为virtual析构函数

C++明确指出当derived对象经过base class指针被删除,而该base class带有一个non-virtual析构函数,则其结果未定义的:局部销毁。

每一个带有virtual函数的class都有一个相应的由函数指针构成的数组vtbl(virtual table). 当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr (virtual table pointer)所指的那个vtbl,编译器在其中寻找适当的函数指针。无端声明析构函数为virtual会增加为vptr的付出的空间补偿。

class SpecialString: public std::string{ //馊主意!

};

SpecialString *pss = new SpecialString(“aaa”);

std::string *ps=pss;

delete ps;//未定义!

如果企图继承一标准容器或任何其他带有non-virtual析构函数的class,拒绝诱惑吧!!!

为你希望成为抽象(不能实体化)的那个class声明一个pure virtual析构函数

class AWOV {

public: virtual ~AWOV() = 0;

};

必须提供一份定义 AWOV::AWOV() {}

不然派生类的析构函数调用base class的析构函数时,不然连接器会报错。

条款08:别让异常逃离析构函数

1.析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能突出异常,析构函数应该捕捉任何异常,然后吞下(不传播)或结束程序。

2.如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数中)执行该操作。

栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。在执行析构函数前如果已经有一个异常,析构函数中又抛出一个异常,将会导致调用标准库 terminate 函数强制从程序非正常退出。

class DBConnection {

public : static DBConnection create();

void close();

};

确保DBConnection不忘记调用close()

class DBConn {

public:

~DBConn(){

db.close();}

private: DBConnection db;

};

DBConn dbc(DBConnection::create());

如果析构函数抛出异常:

1) 强迫结束

DBConn::~DBConn(){

try { db.close();}

catch(…) {

制作运转记录,记下对close的调用失败

std::abort();

}

2)吞下异常

DBConn::~DBConn(){

try { db.close();}

catch(…) {

制作运转记录,记下对close的调用失败

}

3)较佳的策略,客户自己调用普通函数

class DBConn {

public:

void close(){

db.close();

closed = true;

}

~DBConn(){

if(!closed) {

try { db.close(); }

catch(…){

制作运转记录,记下对close的调用失败

abort或吞下异常

}

}

private: DBConnection db;

bool closed;

};

条款09:绝不在构造和析构函数中调用virtual函数

因为这类调用从不下降至derived class (比起当前执行构造函数和析构函数的那层)

class Transaciton {

public:Transaciton();

virtual void logTransaciton() const = 0;

};

Transaciton::Transaciton(){

logTransaciton();

}

class BuyTransaciton:public Transaciton{

public:

virtual void logTransaciton() const;

};

class SellTransaciton:public Transaciton{

public:

virtual void logTransaciton() const;

};

BuyTransaciton b; //基类构造函数调用的是Transaciton::logTransaciton();

BuyTransaciton专属成分还未初始化,最安全的做法就是视而不见,当成Transaciton类。

对象在derived class 构造函数开始执行前不会成为一个derived class。

析构函数类似,在进入base class 析构函数后对象就成为一个base class对象。

解决方案:令derived class 将必要的构造信息上传至base class构造函数,来替换无法使用virtul函数从base class向下调用。

class Transaciton {

public:

explicit Transaciton(const string &loginfo);

void logTransaciton(const string &loginfo) const;

};

Transaciton::Transaciton(const string &loginfo){

logTransaciton(loginfo);

}

class BuyTransaciton:public Transaciton{

public:

BuyTransaciton( parameters): Transaciton( createLogString(parameters)) {…}

private:

static string createLogString(parameters);

};

条款10: 令operator= 返回一个reference to *this

条款11: 在operator= 中处理自我赋值

class Bitmap {…};

class Widget {

private: Bitmap *pb;

};

Widget& Widget::operator=(const Widget &rhs) {

delete pb; //没有进行自我赋值检测

pb = new Bitmap(*rhs.pb);//不具备异常安全,发生异常后,pb指向一块已删除的内存

return *this;

}

解决:证同测试

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 = new Bitmap(*rhs.pb);

delete pOrig;

return *this;

}

另一替换方案确保异常安全,又能处理自我赋值:copy and swap技术

class Widget{

void swap(Widget &rhs);

};

Widget& Widget::operator=(const Widget &rhs){

Widget temp(rhs);

swap(temp);

return *this;

}

Widget& Widget::operator=(const Widget rhs){ //另一较高效写法,by-value

swap(rhs);

return *this;

}

条款12: 复制对象时勿忘其每一个成分

1.copying 函数应该确保赋值”对象内所有成员变量”及“所有base class成分”

2.不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个函数共同调用。

为derived class编写copying函数时,必须复制其base class成分。

class Customer {

public:

Customer(const Customer &rhs);

Customer& operator=(const Customer &rhs);

private: string name;

};

class PriorityCustomer: public Customer {

public:

priorityCustomer(const priorityCustomer &rhs);

priorityCustomer& operator=(const priorityCustomer &rhs);

private: int priority;

};

priorityCustomer::priorityCustomer(const priorityCustomer &rhs)

:Customer(rhs), //调用base class的copy构造函数,如省略,则调用默认的构造函数,基类部分为默认值

priority(rhs.priority)

{ }

priorityCustomer& priorityCustomer::operator=(const priorityCustomer &rhs) {

Customer::operator=(rhs); //可先进行证同测试,成员如有指针

priority = rhs.priority;

return *this;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: