1.explicit:构造函数声明为explicit,避免隐式类型转换,依然可以显示类型转换。
禁止编译器执行非预期的类型换转。
2.Pass-by-Value: 会调用拷贝构造函数,因此对于自定义的类型,最好Pass-by-Reference
01 视C++为一个语言联邦
02 以const,enum,inline替换#define:
宏定义#define只是进行简单的替换,提倡使用const,enum和inline代替#define
#define CALL_WITH_MAX(a, b) f( (a)>(b) ? (a) : (b) ) CALL_WITH_MAX(++a, b) //这种用法会导致++a的次数不确定 //Template inline 函数来替换 template <typename T> inline void call_with_max(const T &a, const T &b) { f ( a>b ? a : b ); }03 尽可能的使用const
const:出现在*左边,表示被指事物是常量,出现在*右边,表示指针自身是常量;出现在两边表示指针和被指之物都是常量。
STL的迭代器的作用类似与T *
指针
std::vector<int> vec; const std::vector<int>::iterator iter = vec.begin(); // iter相当于 T * const ++iter; //这个是错误的 std::vector<int>::const_iterator citer = vec.begin(); // iter相当于const T * *citer= 10; //赋值操作是错误的
const 成员变量:当const和非const函数有相同的实现时,可以利用非const调用const函数
class Textbook { public: const char & operator [] (std::size_t position) const { return Text[position]; } char & operator [] (std::size_t position) { return const_cast<char &>( static_cast<const Textbook&>(*this)[position] ); //将op[]返回值的const 转除为*this 加上const, 调用const op[] } }
const成员函数:
1.使得class中的接口比较容易被理解
2.使得操作const对象成为可能
成员函数只有在不更改对象之任何成员变量(static变量除外)时才可以说是const,可以使用mutable释放掉non-static成员变量的约束,即在申明成员变量前加上mutable即可:mutable int a;
类中定义了2个重载函数,一个为const成员函数,一个为non-const成员函数,如果2个成员函数中有大量的代码重复,可以在非const成员函数中调用const函数使得代码量不重复,调用过程中先将对象static_cast转换为const对象,调用const函数,然后将返回结果利用const_cast转换为非const,记住不能使用const成员函数调用非const成员函数,因为const成员函数承诺不改变对象的逻辑状态
在C++程序设计中,应该对所有对象初始化,以避免不必要的错误。
对象成员变量的初始化发生在进入构造函数本体之前,构造函数内不是初始化,而是赋值。
基类早于派生类被初始化,类的成员变量按其声明顺序被初始化。
C++ 对”定义于不同编译单元内的non-local static 对象”的初始化次序并无明确定义。为免除”跨编译单元之初始化次序”问题,请以local static 对象替换non-local static 对象。
class FileSystem { ... }; FileSystem& tfs() //代替tfs对象 { static FileSystem fs; // 以local static的方式定义和初始化object return fs; // 返回一个引用 } class Directory { ... }; Directory::Directory( params ) { ... std::size_t disks = tfs().numDisks(); ... } Directory& tempDir() // 代替tempDir对象, { static Directory td; return td; }
记住几点:
1.对内置类型对象进行手动初始化
2.构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值造作,其初始化顺序为在class中的申明的顺序
3.在不同的文件中,有多个全局静态变量,现在有一个静态变量需要使用另一个静态变量初始化,这时候我们无法得知这2个全局static变量的初始化顺序,解决方法:将每个全局static对象搬到自己专属的函数内,该对象在函数内被声明为static,这些函数返回一个reference指向它所包含的对象。然后用户调用这些函数,而不直接使用这些对象,换句话说就是全局对象被局部对象代替了。
06 若不想使用编译器自动生成的函数,就明确拒绝
实现对象不被复制,可以将拷贝构造函数,析构函数声明为private,而不去实现。
class HomeForSale { public: ..... private: HomeForSale (cosnt HomeForSale &); HomeForSale &operator = (cosnt HomeForSale &); };
也可以用uncopyable这种基类实现不能复制。
class UnCopyable { public: UnCopyable(); //允许派生类构造和析构 ~UnCopyable(); private: UnCopyable( const UnCopyable &); //阻止复制 UnCopyable & operator= (const UnCopyable &); }; class HomeForSale : private UnCopyable{ ....... };
(1)更好的办法,在编译期发现错误,用下面的类
class UnCopyable { public: UnCopyable(); //允许派生类构造和析构 ~UnCopyable(); private: UnCopyable( const UnCopyable &); //阻止复制 UnCopyable & operator= (const UnCopyable &); }; class HomeForSale : public UnCopyable{ ....... };
07 为多态基类声明virtual析构函数:
(1)带多态性质的base class应该声明一个virtual 析构函数。
如果class 带有任何virtual 函数,它就应该拥有一个virtual 析构函数。这样,当用户delete基类指针时,会自动调用派生类的析构函数(而不是只调用基类的析构函数)。没有虚析构函数的基类使其对象的引用或者指针指向子类的对象时将产生“局部销毁”这一诡异的对象
(2)class的设计目的如果不是作为base class 使用,或不是为了具备多态性(polymorphically) ,就不该声明virtual 析构函数。
这是因为,当一个函数声明为virtual时,C++编译器会创建虚函数表以完成动态绑定功能,这将带来时间和空间上的花销。
不能
class my:public std::vector //严格禁止
public is private have
08 别让异常逃离析构函数:
如果析构函数抛出异常,可能导致其他程序无法正常析构,造成资源泄漏。
(1)析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下它们(不传播)或结束程序。
(2)如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class 应该提供一个普通函数(而非在析构函数中)执行该操作。
09 绝不在构造和析构过程中调用virtual函数
在生成子类对象时,需要先对其基类部分调用基类构造函数构造,而当基类部分的构造函数调用一个虚函数时,而这个虚函数在子类中也有重写,但是在这个过程里还是执行父类的虚函数,这样才能保证父类部分构造出来
在调用基类构造函数中,对象开始时被认为是基类对象,而后才是子类对象,而在析构函数中对象被认为是子类,而后是父类对象。
在构造过程中,基类的虚函数还是指向其自己,可以说还不是虚函数。
在派生类的基类构造过程中,对象的类型是基类,而不是派生类。
如何确保在子类对象构造时父类就会有指定版本的对象被创建呢,可以将函数改为非虚函数,而在子类的构造函数中给父类的构造函数传递信息,这样就可以实现不同子类对象在父类构造中对应不同的父类构造对象。
10 令operator = 返回一个reference to *this
为了实现“连锁赋值“,应令operator= 返回一个reference to *this。
Widget & operator = (const Widget &) { ...... return *this; }
The Design and Evolution of C++ 里面说要返回const T&, 为了防止 (a=b)=c;
Exceptional C++ 说要返回 T&,为了与STL的容器兼容。
所以两种返回方式都可以,看具体需求。
带有赋值操作符的重载函数都应该返回一个指向操作符左侧实参的引用