const
对象或enums
填换#define
。inline
函数替换#define
。const
声明可以帮助编译器找到错误用法。const修饰指针时,如果在
*
的左侧,表示被指物是常量;如果出现在星号右边,说明指针自己是常量;如果在两边,说明被指物和指针自己都是常量。
const方法有两个特性:
- 调用此方法时不能改变类的数据成员的值。
- 非const实例化对象不能调用const声明的方法。
class
中声明的次序相同。non_local static
对象(对象是global
或位于namespace
域内或class
内或file
作用域内),假如某一个non_local static
对象需要调用另一个non_local static
对象时,那个对象不一样已经完成初始化,这时就可能会出现错误。这时使用类似单例设计模式的思想,把static
对象初始化放在某方法内来被动初始化,就可以解决这个问题。当拷贝赋值运算是非法时,编译器会拒绝生成:
- 引用成员数据。
- 常量成员数据。
这样的类不能用于STL容器中,因为容器的元素必须能够拷贝构造和拷贝赋值。
private
并且不予实现。使用像Uncopyable
这样的base class
也是一种做法(多重继承可能会出现问题)。注:如果只是不想使用默认的拷贝构造函数或赋值函数,可以使用C++11提供的
=delete
特性来删除此函数。
带多态性质的基类应该声明一个virtual
析构函数。如果这个类有虚函数,那么它就应该有一个虚析构函数。
举个例子(错误写法):
#include<iostream> using namespace std; class ClxBase{ public: ClxBase() {}; virtual ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;}; virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; }; }; class ClxDerived : public ClxBase{ public: ClxDerived() {}; ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; }; void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }; }; int main(){ ClxBase *p = new ClxDerived; p->DoSomething(); delete p; return 0; }
输出为:
Do something in class ClxBase! Output from the destructor of class ClxBase!
上面这段代码,如果用基类的指针去操作子类的成员,
DoSomething
实现的是多态的性质,会输出子类的函数。但是在delete
析构时,因为父类的析构函数不为虚函数时,子类的析构函数就不会被先调用,就只会删除基类对象,子类对象就会内存泄漏。
如果类的设计目的并不是为了作为基类使用,或不是为了具备多态性,就不要声明virtual
析构函数。
class
应该提供一个普通函数而不是在析构函数中操作。tr1::shared_ptr
和auto_ptr
。使用tr1::shared_ptr
是更推荐的选择,因为其copy
行为比较直观。shared_ptr
)。new
表达式中使用[]
,必须在相应的delete
表达式中也使用[]
。否则都不使用。typedef
操作,否则在new
和delete
操作时就容易出错。new
出来的对象置入智能指针内。例如:
int priority(); void processWidget(std::tr1::shared_ptr<Widget> pw, int priorty); processWidget(new Widget, priority());
如果第4行这么调用的话,程序要做以下3件事:
new Widget priority() std::tr1::shared_ptr()构造函数
而执行顺序并不能确定(和JAVA、C#不同)。
这时如果先执行了
new Widget
,再执行priority()
时出现异常,这时前面new申请的内存就泄漏了。所以上述代码应该改为:
std::tr1::shared_ptr<Widget> pw(new Widget); processWidget(pw, priority());
pass-by-reference-to-const
替换pass-by-value
,前者通常比较高效,不用复制一遍成员数据以及执行父类的构造函数等。而且可以避免切割问题(指使用多态时,如果传递的是值,函数的参数是父类而传入的参数是子类,则执行的将会是父类的方法)。当std::swap
对你的类型效率不高时(如果只需交换对象成员的指针指向,使用std::swap
两个对象则会创建新的对象,效率就会较低),提供一个public swap
成员函数,且这个函数不抛出异常(一般这个函数只对基本类型做操作,也不会抛出异常)。
例如:
class WidgetTmpl { private: int a,b,c; std::vector<double> v; }; class Widget { public: Widget(const Widget& rhs); Widget& operator=(const Widget& rhs) { ... *pImpl = *(rhs.pImpl); ... } private: WidgetImpl* pImpl; };
如果我们想交换
Widget
对象,只需要交换它的pImpl
指针就行了。如果使用std::swap
函数交换,会复制3个Widget
对象,还会复制3个WidgetImpl
对象,效率非常低下。所以我们在
Widget
类中加入swap
函数,就会方便的多。代码如下:class Widget { public: void swap(Widget& other) { using std::swap; // 选择最佳的swap版本,必要 swap(pImpl, other.pImpl); } ... };
再提供一个
non-member swap
来调用:void swap(Widget &a, Widget &b) { a.swap(b); }
调用swap
时应使用using std::swap
,然后调用不带任何命名空间修饰的swap
(见上面代码),这样就可以为要交换的对象选择最佳的swap
形式。
如果class
是模板类型,则特化non-member swap
。
Template<typename T> void swap(Widget<T> &a, Widget<T> &b) { a.swap(b); }
dynamic_casts
。const_cast
、dynamic_cast
、reinterpret_cast
、static_cast
),不要使用旧式转型。优质的C++代码很少使用强制类型转换。
引用
、指针
、迭代器
)指向对象内部。遵守这个条款可增加封装性。inlining
限制在小型、被频繁调用的函数身上。function template
出现在头文件,就将它们声明为inline
。
inline
函数在调用的时候编译器会让它展开,表现出的并不是函数调用的关系。
public
继承意味is-a
。适用于基类的每一件事(类的方法)也适用于派生类,因为每一个派生类对象也都是一个基类对象。这里举个例子:例如
Bird
类和Penguin
(企鹅)类。如果Bird
类有fly
的方法,这时就不能随便使用public
继承,会造成语言的不严谨。如果
Bird
类有fly
方法,则说明此时定义的是一个会飞的鸟类,使用企鹅来继承就不合适。
绝不重新定义继承而来的非虚函数。
例如:
class B { void mf(); }; class D: public B { void mf(); }; D x; B* pB = &x; D* pD = &x; // 下面两行将会可能产生不一样的结果 pB->mf(); pD->mf();
绝不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数却是动态绑定。
如果需要在虚函数使用默认参数的话,可以使用替代方法:令基类内的一个
public non-virtual
函数调用private virtual
函数,后者可以被派生类重新定义,这里我们让public non-virtual
指定缺省参数。
template
参数时,前缀关键字class
和typename
可以互换。typename
标识嵌套从属类型的名称,但不得在基类列和成员初值列内以它作为base class
修饰符。template<typename C> void f(const C& container, // 不允许使用typename typename C::iterator iter) // 一定要使用typename