EFFECTIV C++

1.对内置类型和STL的迭代器和函数对象而言pass-by-value通常比pass-by-reference高效。但是对于模板和类而言,相反。要把C++视为一个联邦,在一个邦国里,要遵守一个邦国的规则,才能获得高效合规的编程。

2.尽量以const, enum, inline替换#define,因为#define记号名称没有进入符号表,导致难以调试。const常量比#define有更小的代码量。第一是定义常量指针( constant pointers) 。由于常量定义式通常被放在头文件内(以便被不同的源码含入) , 因此有必要将指针(而不只是指针所指之对象)声明为const。第二定义类的专属常量时要定义为static。如:

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

NumTurns 是声明式而非定义式。通常C++ 要求你对你所使用的任何东西提供一个定义式,但如果它是个class 专属常量又是static 且为整数类型(integral type,例如ints, chars, bools) ,则需特殊处理。只要不取它们的地址,你可以声明并使用它们而无须提供定义式(一定要求是常量吗??????????)。但如果你取某个class 专属常量的地址,或纵使你不取其地址而你的编译器却(不正确地)坚持要看到一个定义式,你就必须另外提供定义式如下。#define无法提供类的专属常量。如果编译器不支持在类定义体类提供初始值,那么可以使用the enum hack。enum更加想#define,不能取一个enum的地址。

3.#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+l0);//被累加一次

所以要尽量以inline函数代替宏。

4.const Widget* pw;Widget const* pw;效果相同

5.令函数返回const常量一般能降低编码的意外错误,比如对于乘号操作符,Rational a, b, c; (a * b) = c;这是不好的习惯,再比如可能导致这样的错误if (a * b = c)原意假若为比较的话。

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

     当const 和non-const 成员函数有着实质等价的实现时,令non-const 版本调用const 版本可避免代码重复。编码的时候要注意两次强制转换。

7.在C part of C++,最好不要初始化,否则会导致运行期成本,但是在非C部分,最好初始化,所以内置类型和数组不保证初始化。

8.const成员和引用类型成员唯一的初始化机会就是初始化列表。

9.static对象有global对象和定义于namespace作用域,class和函数以及在file作用域内且被声明为static的对象。不包括stack和heap-base对象。函数内的static对象时local static对象,其他事非局部static对象。C++对定义于不同编译单元内的非局部static对象的初始化次序没有明确定义,原因是无法决定顺序。但是C++保证函数内的local static对象会在该函数被调用期间首次遇上该对象定义式的时候被初始化。所以为了避免跨编译单元的初始化次序问题,要以local static对象替换non-local static对象。

10.合成的构造函数已经其他合成函数,只有被调用时才会被编译器创建出来。编译器合成的析构函数默认是非virtual,但是如果基类的析构函数是virtual,那么也为virtual。编译器有时候会拒绝合成某些函数,比如下面这段代码

template<typename T>
class  NameObject
{
public:
      NameObject(std::string& name, const T& value)

private:
      std::string& nameValue;
      const T objectValue;
};
std::string newDog("persephone");
std::string oldDog("Satch");
NameObject<int> p(newDog, 2);
NameObject<int> s(oldDog, 36);
p = s;

11.为了避免编译器自动合成某些函数,可以将该函数声明为private并且不实现。或者继承自Boost的noncopyable类。

12.如果类不当做基类,则不应该令其析构函数为virtual。因为这样就变成了面向对象部分,导致增加一个vptr指针。

13.由于STL中的类都没有虚析构函数,所以不能继承自它们。

14.析构函数不应该抛出任何异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后确保不再次抛出或者直接让程序结束。如果需要对某个函数运行期间的异常做出反应,应该提供一个普通函数而非析构函数中执行该操作。

15.在构造析构期间不要调用virtual函数。要想在构造和析构期间实现多态,只能从下至上使用非virtual函数近似。.

16.注意构造函数之间不能相互调用,这个时候不能重复利用代码。构造函数也不能调用赋值操作符来实现功能。理由。。。。

17.typedef std::string AddressLines[4];
     std::string* pal = new AddressLines;
     delete pal;//wrong
     delete [] pal;//right;

18.以独立语句将new创建的对象存储于智能指针,否则有可能导致内存泄露,比如对于这种情况,·processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
因为这个语句分为三个子句:调用priority,执行 new Widget 调用tr1::shared_ptr构造函数,而这三者的执行顺序是不确定的,so。。。解决这个问题是使用分离语句。

19.绝对不要返回指针或者引用指向一个local stack对象,或者返回指向一个heap-allocated对象,或返回指向一个local static对象的指针或者引用,但是同时需要多个这样的local static 对象,一个是可以的。

20.protected并不比public更具封装性。protected一旦发生改变,和public一样,需要修改大量代码。

21.成员函数带来的封装性比非成员函数带来的封装性要低,此外非成员函数可带来较大的包装弹性,降低编译相依度。

22.如果某些东西被封装,它就不可见。越多的东西被封装,越少的人可以见到它,就有越大的弹性去改变它(类的设计者的角度)以及机能扩充性(比如可以给一个namespace增加更多的功能函数),但是不能对类增加更多的功能函数(当然可以通过继承)。

23.如果需要为某个函数的所有参数(包括this指针所指那个隐参)进行类型转换,那么这个函数必须是个non-member。

24. 通常不能(不被允许)改变std命名空间的任何东西,但是可以(被允许)为标准template制造特化版本。

25.C++只允许类模板偏特化,函数模板不能偏特化(attention, 标准问题。。。。check????????)注意下面两段代码的不同:

26.

template<typename T>
class WidgetImpl { ... }
template<typename T>
class Widget { ... }
namespace std {
   template<typename T>
   void swap< Widget<T> >(Widget<T>& a, Widget<T>& b)
   {
           a.swap(b);
    }
}

namespace std {
      template<typename T>
      void swap(Widget<T>& a, Widget<T>& b)
     {
           a.swap(b);
      }
}

第一个swap是企图对函数模板进行偏特化,第二个是重载了std中swap函数模板。条款25有点不理解。

27.尽可能延后变量定义式的出现。变量的定义是否出现在for循环里面还是外面要看情况一般当循环次数比较大时,尽量放外面。

28.单一对象可能拥有一个以上的地址。因为派生类部分可能和基类部分不连续。

29.注意这段代码:

class Window { // base class
public:
 virtual void onResize() { ... } // base onResize impl
 ...
};

class SpecialWindow : public Window { // derived class
public:
 virtual void onResize() { // derived onResize impl;
  static_cast<Window>(*this).onResize(); // cast *this to Window,
  // then call its onResize;
  // this doesn't work!
  ... // do SpecialWindow-
 } // specific stuff
 ...
};

static_cast<Window>(*this).onResize(); // cast *this to Window,这句话并没有更改当前对象,而是改变当前对象的基类部分的临时副本。应该这么写window::onResize();

30.如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内。

31.避免返回handles(引用,指针,迭代器)指向对象内部,可以增加封装性,帮助const成员函数行为像个const,并将发生dangling handles的可能性降至最低,后面这句话回想下场景。

32.强烈保证"往往能够以copy-and-swap 实现出来,但"强烈保证"并非对所有函数都可实现或具备现实意义。

33.大部分编译器会拒绝比较复杂的inline函数,比如包括了循环和递归,以及virtual函数(理由是因为virtual直到运行了才决定函数,而inline在编译期间替换掉)。

34.编译器通常不对"通过函数指针而进行的调用"实施inlining ,这意味对inline 函数的调用有可能被inlined,也可能不被inlined,取决于该调用的实施方式(这个指的是在同一程序里面两种情况可以同时出现),比如若将该inline函数赋值给一个函数指针,则不实施inline。

35.将大多数inlining 限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)(修改inline函数将导致链接库全部重新编译) 更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。不要只因为function templates 出现在头文件,就将它们声明为inline。

36.支持"编译依存性最小化"的→般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes 和Interface classes 。程序库头文件应该以"完全且仅有声明式" (full and declaration-only forms) 的形式存在。这种做法不论是否涉及templates 都适用。

37.为了让被派生类屏蔽的名字重现可以使用using声明或者转交函数(就是在派生类定义一个成员函数,然后调用基类的成员函数)。

38.可以给纯虚函数提供实现,也可以调用该实现。

39.使用non-virtual interface (NVI) 手法,那是Template Method 设计模式的一种特殊形式。它以public non-virtual 成员函数包裹较低访问性(private 或protected)的virtual 函数。

40.将virtual 函数替换为"函数指针成员变量",这是Strategy 设计模式的一种分解表现形式。

41.以trl::function 成员变量替换virtual 函数,因而允许使用任何可调用物(callable entity) 搭配一个兼容于需求的签名式。这也是Strategy 设计模式的某种形式。

42.将继承体系内的virtual 函数替换为另一个继承体系内的virtual 函数。这是Strategy 设计模式的传统实现手法。

43.绝对不要重新定义继承而来的non-virtual 函数。

44.注意即使对于virtual函数,其默认参数是静态绑定的,而不是动态绑定的,就说基类给一个virtual函数设定了默认参数,派生类重新定义该函数,并且改变了默认参数,当用一个基类指针指向一个派生类对象时,调用该函数时是动态的,但是参数不是动态的。这样做是为了效率。可以考虑使用NVI。

45.C++中可以模拟实现java中的final,使用继承加复合,嵌套类。

46.主要是当protected 成员或者virtual 函数牵扯进来的时候应该使用private继承,否则尽量使用复合。Private 继承意味is-implemented-in-terms of (根据某物实现出)。它通常比复合(composition) 的级别低。但是当derived class 需要访问prot饵ted base class 的成员,或需要重新定义继承而来的virtual 函数时,这么设计是合理的。和复合(composition) 不同, private 继承可以造成empty base  最优化。这对致力于"对象只寸最小化"的程序库开发者而言,可能很重要。

47..C++ 裁定凡是独立(非附属)对象都必须有非零大小,所以空类的对象也有大小,一般官方是插入一个char到空对象,有些编译器为了内存对齐,会插入更多内容。注意class HoldsAnlnt: private Empty {
private:
int X;
几乎可以确定sizeof(HoldsAn lnt)==sizeof(int)。这是空白基类优化,只能在单一继承下可行,多重继承不可行。

48.使用virtual继承速度要慢些,virtual 继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes 不带任何数据,将是最具实用价值的情况。

49.template<typename C> //允许使用"typenarne" (或"class")
     void f(const C& container, //不允许使用可严name"
             typename C::iterator iter); //一定要使用"typename"

50.typenarne 不可以出现在base classes list 内的嵌套从属类型名称之前,也不可在member initialization list (成员初值列)中作为base class 修饰符。例如:
 template<typename T>
class Derived : public Base<T>::Nested {//base class list中不允许"typename"
public:
      explicit Derived(int x)
            : Base<T>::Nested(x)           //member initialization list不允许typename
      {
              typename Base<T>::Nested temp;  //嵌套从属类型名称,既不在base class list中
                                  //也不在member initialization list要加上typername
               ...
       }
        ....
};

51.可在derived class templates 内通过"this->" 指涉base class templates 内的成员名称,或藉由一个明白写出的"base class 资格修饰符"完成。第三个做法就是使用作用域符号,明确告知在基类作用域。这些做法都是对编译器承诺base class template 的任何特化版本都将支持其一般(泛化〉版本所提供的接口,但是假如事实上的特化版本并没有这个成员时,会导致编译错误。如:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
  ...
  void sendClearMsg(const MsgInfo& info)
  {
    write "before sending" info to the log;
    sendClear(info);                          // if Company == CompanyZ,
                                              // this function doesn't exist
    write "after sending" info to the log;
  }
  ...
};

template<>                                 // a total specialization of
class MsgSender<CompanyZ> {                // MsgSender; the same as the
public:                                    // general template, except
  ...                                      // sendCleartext is omitted
  void sendSecret(const MsgInfo& info)
  { ... }

};


template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
  ...
  void sendClearMsg(const MsgInfo& info)
  {
    write "before sending" info to the log;
    this->sendClear(info);                // okay, assumes that
                                          // sendClear will be inherited
    write "after sending" info to the log;
  }
 ...
};
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
  using MsgSender<Company>::sendClear;   // tell compilers to assume
  ...                                    // that sendClear is in the
                                         // base class
  void sendClearMsg(const MsgInfo& info)
  {
    ...
    sendClear(info);                   // okay, assumes that
    ...                                // sendClear will be inherited
  }
  ...
};
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
  ...
  void sendClearMsg(const MsgInfo& info)
  {
    ...
    MsgSender<Company>::sendClear(info);      // okay, assumes that
    ...                                       // sendClear will be
  }                                           //inherited
  ...
};

52.可以使用member function templates (成员函数模板〉生成"可接受所有兼容类型"的函数。

53如果你声明 member templates 用于"泛化 copy构造函数或者泛化assignment操作"你还是需要声明正常的copy构造函数和copy assignemt操作符。

54.当我们编写一个class template,而它所提供之"与此template 相关的"函数支持"所有参数之隐式类型转换"时,请将那些函数定义为"class template 内部的friend函数",假如需要考虑inline问题,则可以定义一个模板外的辅助函数。

55.Traits classes 使得"类型相关信息"在编译期可用。它们以templates 和"templates特化"完成实现。整合重载技术后,traits classes 有可能在编译期对类型执行if...else 测试。

struct input_iterator_tag { };
struct output iterator tag { };
struct forward_iterator_tag: public input_iterator_tag { };
struct bidirectional_iterator_tag: public forward_iterator_tag { };
struct random_access_iterator_tag: public bidirectional iterator_tag { };
针对通用指针步进计算的一个函数模板
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
     if (i ter is a random access iterator) {
        iter += d; //针对random access 迭代器使用途代器算术运算
     else {
        if (d >= 0) { while (d--) ++iter; } //针对其他迭代器分类反复调用++或--
        else { while (d++) - -iter; }
}
下面这段代码就是所需功能,但是有编译错误,并且也不合理
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
     if (typeid(typename std: :iterator_traits<工terT>::iterator_category)
         ==type工d(std::random_access_iterator_tag))
     ...
}
理由在于

下面就是个例子:
std::list<int>::iterator iter;//移动iter 向前走10 个元素:上述实现无法通过编译。

下面这一版advance 便是针对上述调用而产生的。将template 参数IterT 和DistT 分别替换为iter 和10 的类型之后,我们得到这些:
void advance (std: :list<int>: :iterator& iter, int d)
{
     if (typeid(std::iterator_traits<sbd::list<int>::iterator>::iteratorcategory)
         == typeid(std::random access iterator tag}) {
         iter += d; II错误!
     else {
         if (d >= 0) { while (d--) ++iter;}
         else { while (d++) --iter;}
     }
}
问题出在我所强调的那一行代码使用了+=操作符,那便是尝试在一个list<int>::iterator 身上使用卡,但list<int>::iterator 是bidirectional 迭代器(见条款47) ,并不支持+=。只有

random access 迭代器才支持+=。此刻我们知道绝不会执行起+=那一行,因为测试typeid 的那一行总是会因为list<int>::iterators 而失败,但编译器必须确保所有源码都有效,纵使是不

会执行起来的代码!而当iter 不是random access 迭代器时"iter += d" 无效。与此对比的是traits-based TMP 解法,其针对不同类型而进行的代码,被拆分为不同的函数,每个函数所使用

的操作(操作符)都可施行于该函数所对付的类型。

template<typename IterT, typename DistT>
void doAdvance(IterT, T& iter, DistT d ,
               std::random access iterator tag)
{
   iter += d;//这份实现用于random access迭代器
}
template<typename IterT, typename DistT> //这份实现用于bidireetional迭代器
void doAdvance(IterT& iter, DistT d ,
               std::bidirectional iterator tag)
{
   if (d >= 0) { while (d--) ++iter; }
   else { while (d++) --iter; }
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d ,
              std::input iterator tag)//这份实现用于input法代器
{
     if (d < 0 ) {
         throw std: lout of range ("Negative distance");
     }
     while (d--) ++iter;
}


template<typename IterT, typename DistT>
void advance{IterT& iter, DistT d)
{
    doAdvance( //调用的doAdvance版本对iter 之迭代器分类而言必须是适当的。
         iter, d ,
      typename std::iterator tra工ts<工terT>::iterator category()
    );
}

56.set new handler 允许客户指定一个函数,在内存分配无法获得满足时被调用。Nothrow new 是-个颇为局限的工具,因为它只适用于内存分配:后继的构造函数调用还是可能抛出异常。

57.operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理o bytes 申请。Class专属版本则还应该处理"比正确大小更大的(错误)申请"。operator delete应该在收到null 指针时不做任何事。

58.当你写一个placement operator new ,请确定也写出了对应的placementoperator delete。如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄漏。当你声明placementnew 和placementdelete,请确定不要无意识(非故意)地遮掩了它们的正常版本。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值