effective C++(1)

当构造函数不需要用于隐式类型转换,将其明确声明为explicit

区分赋值还是构造的方法:如果一个新对象被定义,一定会有个构造函数被调用,如果没有新对象被定义,就不会有构造函数被调用。

会导致不明确行为:

int* p=0;
cout<<*p;

尽量以编译器替换预处理器,以const对象或enums替换#defines,对于宏以inline函数替换#defines.

通常c++要求你对你所使用的任何东西提供一个定义式,但如果它是个class专属常量又是static且为整数类型,则需要特殊处理,只要不取它们的地址,你可以声明并使用它们而无需提供定义式

class GamePlayer{
 private:
  static const int NumTurns=5;//常量声明式

当取某个class专属常量的地址,则编译器坚持要看到一个定义式,就必须提供另外定义式,由于class常量已在声明时获得处置,因此定义时不可以再设初值。如下:

const int GamePlayer::NumTurns;

#defines并不重视作用域,一旦宏被定义,它就在其后的变异过程中有效,意味#defines不能够用来定义class专属常量

编译器坚持必须在编译期间知道数组的大小。

取一个const的地址是合法的,取一个enum的地址就不合法,取一个#defines的地址通常也不合法

一个良好的用户自定义类型的特征是它们避免无端地与内置类型不兼容。

两个成员函数如果只是常量性不同,可以被重载。

如果函数的返回类型是个内置类型,那么改动函数返回值从来就不合法。

在const成员函数中,改变成员变量的值都是错误的,但是在成员定义前加上mutable就可以在const函数中更改它。

在const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。但是const版本不能调用non-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)[position]);}//为*this加上const
};

移除const动作只可以藉由const_cast完成。

读取未初始化的值会导致不明确行为。确保每一个构造函数都将对象的每一个成员初始化。

C++规定,对象成员变量的初始化发生在进入构造函数本体之前

ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhonrNumber>& phones)
{
 theName=name;          //这些都是赋值,不是初始化
 theAddress=address;    //是先调用default函数后再立即为它们赋予新值
 thePhones=pohones;     //效率较低
 numTimesConsulted=0;
}

ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhonrNumber>& phones)
   :theName(name),
    theAddress(address),     //这些才是初始化
    thePhones(phones),
    numTimesConsulted(0)
   {}

规定总是在初值列中列出所有变量,以免还得记住哪些成员变量可以无需初值。如果成员变量是const胡references,它们就一定需要初值,不能被赋值。在初值列中初始化的顺序要和声明变量的顺序相同。

函数内的static对象称为local static对象,其他static对象称为non-local static对象。所谓编译单元是指产出单一目标文件的那些源码,基本上它是单一源码文件加上其所含入的头文件。

C++保证,函数内的local static对象会在该函数被调用期间,首次遇上该对象之定义式时被初始化,以此来解决跨编译单元之初始化次序的问题。

class FileSystem{
 public:
 ...
 std::size_t numDisks() const;
 ...
};
extern FileSystem tfs;//给客户使用的对象
class Directory{
 public:
 Directory(params);
 ...
};
Directory::Directory(params)
{
 ...
 std::sizez_t disks=tfs.numDisks();
 ...
}

解决方法为:

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;
}

当类没有声明时编译器会为类生成默认构造函数,复制构造函数和析构函数,重载赋值运算符函数,都是公有且为内联函数。

如果你打算在一个内含reference成员的class内支持赋值操作,你必须自己定义赋值运算符函数,面对内含const成员的类,编译器反应也一样,同时,如果某个基类将赋值运算操作符声明为私有,编译器将拒绝为它的派生类生成赋值运算符函数。

当派生类对象经由一个基类指针被删除,而该基类带着一个非虚析构函数,那么结果是基类的成员被销毁而派生类的成员没有销毁并且派生类的析构函数也没能调用起来,造成局部销毁的诡异情况。

如果类不含虚函数,通常表示它并不意图被用作一个基类,那么就不要给它设置虚析构函数

欲实现出虚函数,对象必须携带某些信息,主要是用来在运行期决定哪一个虚函数该被调用,通常是由一个所谓的vptr指针指出,vptr指向一个由函数指针构成的数组,称为vtbl,每一个带有虚函数的类都有一个相应的vtbl,当对象调用某一虚函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl-编译器在其中寻找适当的函数指针。

不要继承一个标准容器的类,否则析构时一定会出问题!

当你定义了一个纯虚析构函数时,必须为纯虚析构函数提供一份定义,如:AWOV::~AWOV(){}

只有当类内至少含有一个虚函数时,才为它声明虚析构函数。

在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为,因此程序遭遇一个于析构期间发生的错误后无法继续执行,强波结束是个合理选项,它可以阻止异常从析构函数传播出去

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

基类构造期间虚函数绝不会下降到派生类阶层,在基类构造期间,虚函数不是虚函数,在派生类对象的基类构造期间,对象的类型是基类而不是派生类,对象在派生类构造函数开始执行前不会成为一个派生类对象,同样适用于析构函数。

不要在构造函数和析构函数中调用虚函数。

令operator=返回一个reference to *this,也适用于所有赋值相关运算

自我赋值问题有可能出现如下代码所示,operator=函数内的*this和rhs有可能是同一个对象,果真如此,delete就不只是销毁当前对象的bitmap,它也销毁rhs的bitmap,这样将会指向一个已经被删除的对象

Widget& Widget::operator=(const Widget& rhs)
{
 delete pb;
 pb=new Bitmap(*rhs.pb);
 return *this;
}

解决方法有如下几种

Widget& Widget::operaotr=(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;
 return *this;
}
class Widget{
 ..
 void swap(Widget& rhs) //交换*this和rhs的数据
{
 ...
};
Widget& Widget::operator=(const Widget& rhs)
{
 Widget temp(rhs);      //为rhs的数据制作一个复件
 swap(temp);            //将*this数据和上述复件的数据交换
 return *this;
}
Widget& Widget::operator=(Widget rhs) //rhs是被传对象的一份复件 有时在函数参数构造阶段以by value的方式可能产生高效代码
{
 swap(rhs);                           //将*this的数据和复件的数据交换
 return *this;
}


猜你喜欢

转载自blog.csdn.net/weixin_38893389/article/details/79472665