c, object-oriented cpp, template cpp, STL
使用 const, enum, inline替换define, 因为我们宁可以编译器替换预处理器。
//定义常量char const char* const autoName = "sm"; const string authorName ("sm");在类中定义作用域仅在类中的常量:
class GamePlayer{ private: static const int Num = 5; } //如果编译器要求看到定义式(不要在设初值): const int GamePlayer::Num;还有一种方法:
class GamePlayer{ private: enum { Num = 5}; }同样,要用inline 替换掉 define:
template <typename T> inline void callwithMax(const T& a, const T& b) { f(a > b ? a : b); }注意下下面的几个区别:
char* p = greeting; const char* p = greeting; // const data; char* const p = greeting; //const pointer; const char* const p = greeting; //both const;cpp中的无初值初始化的case还是比较复杂的,有时候会初始化为0,有时候不会。所以建议手工进行初始化。 注意,初始化和赋值是有区别的:
class ABentry{ public: public: ABentry(const std:: string & name); private: std::string a; int num; }; ABentry::ABentry(const std:: string & name){ a = name; num = 0; } //这是赋值 ABentry::ABentry(const std:: string & name) : a(name), num(0) //这是列表初始化 {}通常来讲,对于内置类型,用赋值的方法,会先调用默认构造函数,然后赋值,这样做比直接使用列表初始化复杂,因为列表初始化直接拿来实参进行拷贝构造,效率高很多。当然还有其他的一些情况分析,总之建议用列表初始化,不要用赋值。当然某个特殊情况下可以单独分析,比如有好多构造函数,为了避免重复初始化工作,可以将一些值通过一个私有函数进行赋值操作。
编译器会默认创建default构造函数,copy构造函数和copy assignment操作符:
class Empty { }; //其实你已经做了以下操作: class Empty{ public: Empty() {...} Empty(const Empty& rhs) { ...} ~Empty(){ ...} Empty& operator=(const Empty& rhs){... } };上面条例表明,cpp会自动生成就算你不去声明,我们的惯用伎俩就是将这两个声明为private并不去定义他们。
class Empty{ public: Empty() {...} ~Empty(){ ...} private: Empty(const Empty& rhs); //只有声明 Empty& operator=(const Empty& rhs); };或者还有个微妙的做法就是声明一个专门用来禁用拷贝的类,需要他的时候把他继承过来:
class noncopyable{ public: noncopyable() { } ~noncopyable(){ } private: noncopyable(const noncopyable& ); //只有声明 noncopyable& operator=(const noncopyable& ); }; class Empty:: private noncopyable{ ... };这里面继承关系不一定要用public, 同时noncopyable的析构函数也不一定要virtual,(因为我们并不是为了实现什么多态,就像很多STL容器作为base class使用,但并不是要实现多态),多态基类是一定要声明virtual desctrutor,下面会有条款。最后,我们还可以用boost直接引入noncopyable。
具有多态性质的base class要声明虚析构,如果class带有任何virtual函数,他也应该拥有一个虚析构。
如果class的目的不是为了实现多态,或者不是为了用作base class,那就不需要虚析构。
虽然不禁止析构函数抛出异常,但是最好不要这样做,比如有这样一个vector, 存放着是个类,当这个vector要被销毁时, 如果在类的析构中抛出异常, 可能当抛出第二个异常的时候,程序就会导致不明确的行为了.
解决方案是:要么强制结束程序,要么将异常吞掉.
不过这里仍然推荐一个新的方案,就是将析构函数所要做的事情重新定义一个新的函数,在析构函数中调用这个函数,并记录下对该函数的调用失败情况. 这样做就把析构所执行的功能交给了用户,当用户没有执行该功能时,析构也能调起该功能,并进行记录.
通常来讲,对于运算符重载,推荐要这么做:
class Widget { public: ... Widge& operator=(const Widget& rhs) { ... return *this; } };为了不让自我赋值的事情发生,我们需要证同测试。
Widget& Widget::operator=(const Widget &rhs) { if(this == &rhs) return *this; delete pb; pb = new Bitshop(*rhs.pb); return *this; }在自己定义拷贝构造函数的时候,要记得为derived class撰写拷贝构造函数,这些成分往往是private的,所以应该让derived class的拷贝构造函数调用相应的base class函数、
在释放对象内存的过程中,可以利用auto_ptr和shared_ptr确保对象释放。对于如下的片段的释放内存方案是有风险的:
void f() { Investment * pinv = createInvestment(); ... delete pinv; }我们无法确保。。。中不会抛出什么异常导致最后的delete部分无法执行,这会带来内存泄露的风险。利用智能指针可以帮助我们解决这个问题:
void f() { std::auto<Investment> pinv(createInvestment()); ... }这样做示范的是以对象管理资源的两个关键想法: 1,获得资源后立刻放进管理对象内。 2,管理对象运用析构函数确保内存释放。 但是有的STL容器是不支持auto_ptr的,他的替代品是reference-counting smart pointer. 比如tr1::shared_ptr. 两者都在其析构函数中做delete, 而不是delete[ ]. 所以注意这对动态分配而得的array身上使用这两个ptr是不行的。
???
???
???
防止误用的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户资源管理责任。
如果我们现在有这些类的声明和定义在这里:
class Person{ public: Person(); virtural ~Person(); private: std::string name; std::string address; }; class Student: public Person{ public:: Student(); ~Student(); private:: std::string schoolname; std::string schooladdress; };然后做以下操作:
bool validateStudent(Student s); Student plato; bool platoisok = validateStudent(plato);这里做了传值操作,总计发生了六次构造和析构函数的行为。 换成传引用将避免发生这些动作。
bool platoisok = validateStudent(const Student &plato);另外的好处在于,传引用可以避免对象切割问题: 如果有以下的类:
class Window{ public: ... std::string name() const; virtual void display() const; }; class Windowscore: public Window{ public: virtual void display() const; }; void printname(Window w){ std::cout<<w.name(); w.display(); }如果有以下的操作:
Windowscore wind; printname(wind);就算这里想使用的是windscore的display函数,但是传值方式会发生对象切割,给你提供的display函数属于window, 如果换成传引用,就可以自动识别传进来的类型:
void printname(Window& w){ std::cout<<w.name(); w.display(); }举个例子: Ration是个类,能够通过 Ration(a,b)进行初始化。
const Ration & operator* (const Rational& lhs, const Rational & rhs){ Rational result(lhs.n * lhs.n, lhd.d *lhd.d); return result; }这样做是危险的,因为result是局部变量,离开函数后就会被消亡,所以是无法返回的。 你可以这么做:
inline const Ration operator* (const Rational& lhs, const Rational & rhs){ return Rational(lhs.n * lhs.n, lhd.d *lhd.d); }可以看一下以下的几个对比方式:
//有这么一个time类 Time::Time(int h, int m) { hours = h; minutes = m; } //传递引用的overload // Time &Time::operator+(Time &t) // { // hours += t.hours; // minutes += t.minutes; // return *this; // } // 直接return 的方式 Time Time::operator+(Time &t) { return Time(hours + t.hours, minutes + t.minutes); } // 实现过程中定义一个类,在把他返回出来,返回的必须是值不能是引用 // Time Time::operator+(Time &t) // { // Time sum; // sum.hours = hours + t.hours; // sum.minutes = minutes + t.minutes; // return sum; // }从封装的角度来说,只有private(封装)和其他(不封装),事实上,protectd并不比public更具封装性。
越少的函数能够对数据进行访问,数据的封装性就越好,所以我们希望将一些组合功能(相近的),比如以浏览器为例,一系列清理功能组合成一个函数,调用类中的小功能(比如清理内存,清理浏览记录等等),尽管我们也可以写一个成员函数clear_all将这些子功能包含在一块,但是更好的办法是通过命名空间将一个非成员函数定义出来,去组合这些子功能,因为这样最大程度的保有了数据原有的封装性。
我们定义一个有理数类,并且重载 * 运算符。
class Rational { public: Rational(int numerator = 0, int denominator = 1); int numerator() const; int denominator() const; const Rational operator* (const Rational & rhs) const; private: ... };可想而知,我们可以
Rational one(1,8); Rational two(1,2); Rational result = one * two; // 行 result = 2 * one; // 不行为了解决这个问题,将重载运算符定义为non-member 函数
class{ ... }; const Rational operator *(const Rational & lhs, const Rational & rhs) { return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); }这样一来:
Rational one(1,8); Rational two(1,2); Rational result = one * two; // 行 result = 2 * one; // 行???
只要你定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序执行到这个变量的定义式时,你便要承受构造成本。所以,如果这个变量未被使用,就应该尽量避免他被定义,因为你白白花费了构造和析构成本。更明确的,不止应该延后变量定义,更应该尝试延后这份定义直到能够给他赋初值为止。对于循环问题,如下:
// 定义于循环之外 Widget w; for (int i = 0; i < n; i++){ w = i; ... } // 定义于循环之内 for (int i = 0; i < n; i++){ Widget w (i); ... }注意上面的Widget w被直接通过拷贝构造了获得了初值,而不是先通过默认构造,然后再进行赋值行为, 如下所示。 这也是一种提高效率的办法。
Widget w; w(i);对于上面的循环问题,需要确定: 做法A: 1个构造,1个析构,n个赋值 做法B: n个构造,n个析构 到底那个速度快。 当然做法A让widget的作用域更大了,这也是和程序的可理解性和易维护性相矛盾的。总体上说,除非你知道 (1)你知道赋值成本比构造+析构成本低,或者(2)你正在处理效率敏感度很高的部分,否则你就应该使用做法B。
???
尽量避免返回handles(reference, pointer,iterator)指向对象内部,如果一定要使用,在前面加上const,这样开放了一部分封装性的权限,但是依旧保证了对象内部数据的只读不写的特性。
???
将大多数inlining限制在小型被频繁调用的函数身上。 不要只因为function templates出现在头文件就将它声明为inline.
原则上就是相依于声明式而不是定义式
很简单,不用解释
derived class 内的名称会遮掩base class内的名称。如果依然想要使用base class 中的名称,就可以使用using名称空间,如果base class中有重载函数,而你只想要用其中的某个,则可以使用转交函数:
class Base{ public: virtual void mf1() = 0; virtual void mf1(int); }; class derived : private Base { public: virtual void mf1() { Base:: mf1();} };如果要使用base class中的函数、
。。。 class derived : public Bae{ public: using Base::mf1; using Base::mf3; // 使用using引入名为 mf3的base class 中的所有函数 };???
