Effective C++笔记(1)

来源:互联网 发布:里约奥运会数据 编辑:程序博客网 时间:2024/06/05 14:27

    一、让自己习惯C++

        (1)C++是一个语言联邦,主要包括四部分:C(面向过程、基于对象?)、Object-Oriented C++、Template C++、STL。

         记住这四个语言层次,当你从某个语言切换到另一个,导致高效编程守则要求你改变策略时,不要感到惊讶。例如对内置(也就是C-like)类型而言pass-by-value通常比pass-by-reference高效,但是你从C part of C++移到Object-Oriented C++,有用用户自定义(user-defined)构造函数和析构函数的存在,pass-by-reference-to-const往往更好。运用Template C++时尤为如此,因此彼时你甚至不知道所处理的对象的类型。然而一旦跨入STL你就会了解,迭代器和函数对象是在C指针之上塑造出来的,所以对STL的迭代器和函数对象而言,旧式的C pass-by-value守则再次受用。

         请记住:C++高效编程守则视状态而变化,取决于你使用C++的哪一部分。

    

         (2)使用const常量或enum来替代#define常数;使用inline函数替代#define函数。

            class GamePlayer{

               private:

                   static const int NumTurns;

                   enum {  NumTurns = 5 };

                    int scores[NumTurns];

           }

           const int GamePlayer::NumTurns = 5;

         

            template<typename T>

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

            {

                f(a > b ? a : b);

            }

           请记住:

           对于单纯常量,最好以const对象或enum替换#defines。

          对应形似函数的宏(macros),最好改用inline函数替换#defines。


          (3)尽可能使用const。

           const可以修饰全局变量、局部变量,表示变量不可以改变。

           const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。

           const转型:

           class TextBlock {

            public:

               ...

               const char& operator[](std::size_t position)  const

               {

                   ...

                   ...

                   return text[position];

               }

              

               char& operator[](std::size_t position)

               {

                    return const_cast<char&>(  //将op[]返回值的const转除

                       static_cast<const TextBlock&>(*this)  //为*this加上const

                        [position]);  //调用const op[]

               }

           }

           请记住:

           将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数的返回类型、函数参数、函数返回类型、成员函数主体。

           编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)。

           

           (4)确定对象被使用前已先被初始化。

             永远在使用对象之前先将它初始化。对应无任何成员的内置类型,你必须手工完成此事。至于内置类型以为的任何其他东西,初始化责任落在构造函数(constructors)身上。规则很简单:确保每一个构造函数都将对象的每一个成员初始化。

           class PhoneNumber { ... };
 class PhoneNumber { ... };
class ABEntry
{
public:
ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};


ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones)
{
theName = name;          //这些都是赋值(assignments)而非初始化(initializations)。
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}


ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones)
:theName(name),theAddress(address),thePhones(phones),numTimesConsulted(0) //现在,这些都是初始化(initializations)
{}  //现在,构造函数本体不必有任何动作

     如果成员函数是const或referneces,它们就一定需要初值,不能被赋值。

      总是使用成员初值列,这样做有时候绝对必要,且又往往比赋值更高效。

      “不同编译单元内定义之non-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;
}       

定义成Singleton模式。

       请记住:

            为内置型对象进行手工初始化,因为C++不保证初始化它们。

            构造函数最好使用成员初始列(member initlialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们的class中的声明次序相同。

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


    二、构造/析构/赋值运算

     (5)请记住:

       编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。

      (6)若不想使用编译器自动生成的函数,就该明确拒绝

         class HomeForSale{
public:
...
private:
...
HomeForSale(const HomeForSale&);
HomeForSale& operator=(const HomeForSale&);
};

class Uncopyable{
protected:             //允许derived构造和析构
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);       //阻止copying
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale:private Uncopyable{ //class不在声明
...                               //copy构造函数或
};                                    //copy assign操作符

    请记住:

     为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

       

        (7)请记住:

          polymorphic(带多态性质的)base classes应该声明一个vitual析构函数。如果class带有任何vitual函数,它就应该拥有一个virtual析构函数。

          Class的设计目的如果不是作为base classes使用,或不是为了具备多态性(polymorphically),就不该声明virtual析构函数。

           (8)别让异常逃离析构函数

            class DBConnection {
public:
...
static DBConnection create();


void close();
};


class DBConn{
public:
...
void close()
{
db.close();
closed = true;
}


~DBConn()
{
if (!closed)
{
try
{
db.close();
}
catch (...)   //如果关闭动作失败
{             //记录下来并结束程序 
         //或吞下异常    
}
}
}


private:
DBConnection db;
bool closed;
};

          请记住:

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

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

        

         (9):绝不在构造和析构过程中调用virtual函数

         based class构造期间virtual函数绝不会下降到deriverd classes阶段。在based class构造期间,virtual函数不是virtual函数。

        class  Transaction {

        public:

              explicitTransaction(const std::string& logInfo);

              void logTransaction(const std::string& logInfo) const;

         }

         Transaction::Transaction(const std::string& logInfo)

          {

             ...

             logTransaction(logInfo);

          }

          

          class BuyTransaction: public Transaction {

           public:

                 BuyTransaction(parameters)

                   :Transaction(createLogString(parameters))  //将log信息传给based class构造函数

                  { ... }    

                  ...

           private:

                static std::string createLogString(parameters);

         };

         换句话说由于你无法使用virtual函数从base classes向下调用,在构造期间,你可以藉由“令derived classes将必要的构造信息向上传递至based class构造函数”替换之而加以弥补。

         在本例中,令createLogString函数为static,也就不可能意外指向“初值未成熟之BuyTransaction对象内尚未初始化的成员变量”。这很重要,正是因为“那些成员变量处于未定义状态”,所以“在based class构造和析构期间调用的virtual函数不可下降至derived classes”。

         请记住:

          在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derviecd class(比起当前执行构造函数和析构函数的那层)。

         

        (10):令operator=返回一个reference to *this

          为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参。这是你为classes实现赋值操作符时应该遵循的协议:

          class Widget{

           public:

                ...

           Widget& operator=(const Widget& rhs)  //返回类型是个reference指向当前对象

            {

                ...

                return *this;                                                        //返回左侧对象

            }

            ...

           };

           这个协议不仅适用于以上的标准赋值形式,也适用于所有赋值相关运算。

           请记住:

           令赋值(assignment)操作符返回一个reference to *this。


          (11):在operator=中处理“自我赋值”

           “ 证同测试”:

           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;          

            }


             copy and swap技术:

             class Widget {

                 ...

                 void swap(Widget& rhs);             //交换*this和rhs的数据

                 ...

             };

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

             {

                    Widget temp(rhs);       //为rhs数据制作一份复件(副本)

                    swap(temp);                //将*this数据和上述复件的数据交换。

                    return *this;

             }

             请记住:

              确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的顺序、以及cope-and-swap。

              确定任何函数如果操作一个以上的对象,而其中多个对象时同一个对象时,其行为仍然正确。

            

              (12):赋值对象时勿忘其每一个成分

               任何时候只要你承担起“为derived class撰写copying函数”的重责大任,必须小心地也复制其base class成分。

               PriorityCustomer::PriorityCustomer(constPriorityCustomer& rhs)

                   : Customer(rhs),priority(rhs.priority)      //调用base class的copy构造函数

               {

                      logCall("PriorityCustomer copy constructor");

               }

               PriorityCustomer& PriorityCustomer::operator=(constPriorityCustomer& rhs)

               {

                      logCall("PriorityCustomer copy assignment operator");

                      Customer::operator=(rhs);

                      priority = rhs.priority;

                      return *this;

               }

               本条题目所说的“复制每一个成分”现在应该很清楚了。当你编写一个copying函数,请确保(1)复制所有local成员变量,(2)调用所有base classes内的适当的copying函数。

             这两个copying函数(copy assignment操作符和copy构造函数)往往有近似相同的实现本体,这可能会诱使你让某个函数调用另一个函数以避免代码重复。这种精益求精的态度值得赞赏,但是令某个copying函数调用另一个copying函数却无法让你达到你想要的目标。如果你发现你的copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数给两者调用。这样的函数往往是private而且常被命名为init。这个策略可以安全消除copy构造函数和copy assignment操作符的代码重复。

           请记住:

            Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。

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

0 0
原创粉丝点击