Effective C++ 改善程序设计的55个具体做法(一)

一 让自己习惯C++ (Accustoming Yourself to C++)

条款01:视C++为联邦型语言(View C++ as a federation of languages)

C++四个主要的次语言:

@ C 说到底C++仍是以C为基础

@ OBject-Oriented C++  这部分也是Cwith Class 的诉求 包括class 继承 封装 多态 虚函数 等

@ Template C++ :C++范式编程部分

@STL 标准程序库部分 包括标准容器算法迭代器函数对象等

当你从某个语言层次切换到另一个时导致搞笑编程守则要求你改变策略时,不要感到惊讶

请记住:

C++高效编程守则视状态而变化,取决于你使用C++的那一部分

条款02:尽量const,enum,inline替换#define(prefer const,enums and inlines to #define)

宁可以编译器替换预处理器#define 不被视为语言的一部分

当我们以常量替换#defines 有两种特殊情况:

第一:定义常量指针:常量定义式通常被放在头文件内,有必要将指针声明为const

例如定义一个常量的char*-base字符串,你必须写const 两次:const char* const  authorName="Scott Meyers";

使用 string会更好 const string authorName=“Scott Meyers”;

第二:class 的专属常量

为了将常量的作用域限制在class内你必须让他成为class的一员为确保此常量只有一份你必须确保它成为static 成员

class GamePlayer
{
   private:
  static const int NumTurns=5//常量声明式
  int scores[NumTurns];//使用该常量
};

如果你取某个class 专属常量的地址或你不取其地址而编译器坚持要看到定义式你就必须提供定义式:

const int GamePlayer::NumTurns;(把此放进实现文件中)

对于旧的编译器可能不支持static成员在声明时获得初值你可以将初值放到定义式中:

class GamePlayer2
{
   private:
  static const int NumTurns;//常量声明式
 
};

const int GamePlayer2::NumTurns=5;

如果你的编译器不允许static 整数 class 常量完成 in class 的初始值设定可改用 “the enum hack”补偿做法 结果看起来像这样:

class GamePlayr3

{

     private:

    enum{NumTurns=5};//其在某些方面像#define而不像const如果你不想让别人获得一个point或refer指向你的某个整形常量enum是个好的选择

   int socres[NumTurns];

}

第三以#define实现宏看起来像函数但不会招致函数的调用带来的额外开销:

#define Call_With_Max(a,b)    f((a)>(b) ? (a):(b))

请记住:无论何时你写出这样的宏一定要记住所有的实参必须加上小括号否则某些人调用时可能不方便,但是纵是这样依然会有困惑产生:

int a=5,b=0;

Call_With_Max(++a,b);

Call_With_Max(++a,b+10);

这里的a的递增次数竟然取决于他和谁比较

通常情况下我们更希望通过template inline函数来解决像这样的问题

template<tyname T>

inline void callWithMax(const T& a,const T& b)

{

    f(a>b?a:b);

}

请记住:

@ 对于单纯的常量最好以const对象或enums 替换#defines

@对于形似函数的宏,最好改用inline函数替换#defines

条款03:尽量使用const(Use const Whenever possible)

const 关键字的多才多艺

class 外部修饰global或namespace 作用域中的常量或修饰文件,函数,或者区域作用域中声明为static的对象,也可以用它修饰classes内部的static和non_static 成员变量,面对指针,你也可以指出指针自身,指针所指物,或者两者都是const

char greeting[]="hello"

char* p=greeting;//non-const pointer,non-const data

const char* p=greeting//non-cosnt pointer  注意其和 char const* p等价

char* cosnt p=greeting ;//const pointer,non-const data

const char* cosnt p=greeting;//all const

迭代器的作用就像T*指针生命迭代器为const就和声明指针const一样

const威力用法是面对函数声明时的运用

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

const Rational operator*(const Rational& lhs,const Rational& rhs);

在此声明为const的返回值可以组织进行这样的操作:(a*b)=c;

const 成员函数

此目的是确保该成员函数作用于const对象上

基于两个原因:

1 他们是class 接口比较容易理解,可以知道那个函数可以改动对象,那个不可以

2他们使操作const对象成为可能,这是编写高效代码的关键

注意一个事实:两个成员数如果只有常量性不同可以被重载这是一个很重要的C++特性

通过例子进一步讲解:

class TextBlock
{
public:
const char& operator[](szie_t position) const
{
return text[position];
}
char& operator[](szie_t positions)
{
return text[posiitions];
}
private:
string text;
};

operator[]可以被这样使用

TextBlocks tb("hello");

cout<<tb[0]//调用非const函数   也可以这样 tb[0]=‘a‘

const TextBlocks("hello");

cout<<ctb[0]调用const函数


但更多的我们会这样使用

void print(const TextBlocks& ctb)

{

     cout<<ctb[0]<<endl;

}

需要注意的如果operator[]函数返回的是char 不是char& 则一下调用会失败

tb[0]=‘a‘;   因为C++不允许修改内置类型的函数返回值

许多函数虽然不十足具备const性质但是却能通过bitwise测试(即该成员函数不会修改成员变量的任何一个参数),更具体的说一个更改了指针所指物的成员函数虽然不能算是const但只有指针隶属于对象那么函数为bitwise不会引发编译器的异议

例如:

class TextBlock
{
public:
char& operator[](szie_t positions)
{
return text[posiitions];
}
private:
char* ptext;
};

通过下面操作:

const TextBlocks ctb("ssss");

char*pc =&ctb[0];

*pc=‘j‘;//   最终还是改变了他的值(这种情况就是所谓的logic constness 的主张即一个const成员函数可以修改他所处理对象的某些bits但只有客户端侦测不出来的情况下)

class TextBlock
{
public:
char& operator[](szie_t positions)
{
return text[posiitions];
}

        size_t length()consst;
private:
char* ptext;

        mutable size_t textlength;//mutalbe 表明这些变量可能总是会被修改即使在const函数中

        mutable bool lengthValue;
};

   size_t TextBlocks::length()

{

    if(!lengthValue)

    {

       textLength=strlen(pText);

     lengthValue=true;//及时在cosnt中我们也修改了其值

   }

}

即若编译器坚持bitwise检测我们可以通过上面例子加一解决

const 和non-const 成员函数避免重复

可以通过一个函数调用另一函数来避免重复(进行常量性转移)

class TextBlock
{
public:
const char& operator[](szie_t position) const
{

              ...............................//其他操作
return text[position];
}
char& operator[](szie_t positions)
{

                return const_cast<char&>(static_cast<const TextBlocks&>(*this) [positions];//将op[]返回值cosnt属性去掉为*this加上cosnt调用 const op[]
}
private:
string text;
};

令const版本调用non-cosnt版本以避免重复不是你该做的事

请记住:

@ 将某些东西声明为const可以帮助编译器侦测错误用法。

@ 编译器强制实施bitwise cosntness 但你编写的程序时应该使用概念上的常量性

@当cosnt和non-const成员函数有实质等价实现时令non-const调用const版本可以避免代码重复


条款04 确保对象在使用前以被初始化(make sure that objects are initialized before they are used)

请确保每个构造函数豆浆对象的每个成员初始化

确保你区分了初始化和赋值

ClassA::ClassA(const string& name,cosnt string& address)

{

     thename=name;

    theaddress=address;//在此时赋值而非初始化哦

    numTimes=10;//其在对象内为int内置类型 在此为初始化

}

构造函数内的成员是在其default构造函数被执行时被初始化的比进入ClassA本体更早(因为部分成员为string类型)但对于内置类型不为真通常我们会采用一下写法来提高程的执行效率:即用初始化代替赋值(首先避免了default构造函数的调用,而是直接采用copy构造,其次避免了copy assignment的调用)

采用成员初始化列表:

ClassA::ClassA(cosnt string& name,const string& address):thename(name),theaddress(address),numTimes(10){}

同样你可以让你的default构造函数 变成成员初始化形式

ClassA::ClassA():thename(),theaddress(),numTimes(10);

对内置类型放在{}内和放在在成员初始化列表内效果是一样的但是对于成员变量是const或reference一定需要初值不能被赋值了 其次就是初始化顺序 base class 早于 derived class ,class内的成员变量总是以声明的次序被初始化(因此为提高程序效率请按声明次序初始化成员变量)

对于static 对象的一些说明

local static 对象:包括global对象,定义于namespace作用域内的对象在class内函数内以及file作用域内被声明为static的对象

除上面外的static对象被称为non-logic static对象

他们会带来什么问题哪???(初始化顺序问题,可能导致初始化的调用未被初始化的对象)

看一个例子有助于理解

class FileSystem

{

    .............................

   size_t numDisks() const;

};

extern FileSystem tfs;

现在我们有个class用以处理 文件系统的目录那么我们将用上FileSystem

class Directory

{

   public:

   Directory(params);

  ......................

};

Directory::Directory(params)

{

    szie_t disks=tfs,numDisks();//使用tfs对象

}


假如用户决定创建个临时目录

Directory tempDir(params)  除非tfs在tempDir之前被初始化否则问题就来了

如何确保tfs在tempDir之前被初始化哪?

将每个non-local static 对象搬至自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回reference指向他所含的对象,用户直接调用这些函数而不是直接指涉这些对象(其实这就是 设计模式中的单例模式了)

来看看对上面例子的更改实现

class FileSystem

{

    .............................

   size_t numDisks() const;

};

FileSystem& tfs()

{

   static FileSystem fs;

   return fs;

}

class Directory(){....}//同前

Directory::Directory(params)

{

    szie_t disks=tfs().numDisks();//使用tfs对象

}

Diectory& tempDir()//reference-returning

{

   static Directory td;

   return td;

}

reference-returning 函数往往十分单纯,但从另一角度看内含static对象的事实在多线程系统中带有不确定性,任何一种non-cost static 对象不管是local或者non-local 在多线程环境下等待某些事发生都会有麻烦我们可以通过:在程序的单线程启动时手工调用所有的reference-returning函数这个可以消除初始化有关的竞速问题.(前提是其中有着一个对对象而言合理的初始化次序即不产生循环等候初始化的问题(A必须在B之前初始化而A能否初始化成功又取决于B是否已初始化))

请记住:

@ 为内置对象进行手工初始化,因为c++不保证初始化他们

@构造函数最好使用成员初始化列而不是在构造函数本体内使用赋值操作。初值列列出的成员变量其排列次序应该和他在class中声明的次序保持一致

@为免除“跨 编译单元之初始化次序”问题请以local static 对象替换non-local static 对象


转载请注明出处:http://blog.csdn.net/luguifang2011/article/details/42362243













郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。