Effective C++ 读书笔记(二)

条款四:确认对象使用前已经被初始化

读取未初始化的值会导致不明确的行为。

最佳的处理方法是:永远在使用对象之前先将它初始化。对于无任何成员的内置类型,我们必须手动完成初始化。

int x=0;
const char* c="hellowoeld";

double b;
cin>>b;

在内置类型以外的任何东西,初始化的责任应该有构造函数完成。

确保每一个构造函数都将其每一个成员变量初始化。

不要混淆赋值和初始化。

下列函数是在赋值,c++规定,对象的成员变量的初始化发生在进入构造函数本体之前。

class PhoneNumber
{ ...
};
class ABEntry
{
public: 
PhoneNumber(const string name,const string address,const list<PthoneNumer> phone);
private:
string thename;
string theaddress;
list<PhoneNumber> thePhone;
};
PhoneNumber::PhoneNumber(const string name,const string address,const list<PthoneNumer> phone)
{
    thename=name;
    theaddress=address;
    thePhone=phone;
}

 正确的写法如下,使用成员初始列替换赋值操作。

PhoneNumber::PhoneNumber(const string name,const string address,const list<PthoneNumer> phone): thename(name),
    theaddress(address),
    thePhone(phone)
{
   
}

两个代码运行结果相同,但第二个通常效率更高。因为基于赋值的版本会先调用default构造函数为成员变量赋初值,然后在对其赋新值。对大多数类型而言,先调用default构造函数在调用copy构造函数效率是比直接调用copy构造函数效率低的。有些内置类型中,初始化和赋值的成本相同,但为了统一而言,一般都通过成员初始列来构造,即使你想要一个default初始化的参数,也可以用成员初始列来实现。

PhoneNumber::PhoneNumber(): 
    thename(),
    theaddress(),
    thePhone()
{
   
}

上述代码即调用thname、theaddress等的default构造函数。

规定总是在初始列中列出所有成员变量,以免还得记住那些成员变量可以无需初值。

有些情况下成员变量属于内置类型,也一定得使用初始列,如const和引用必须要初始化,而不能被赋值。为了避免记住成员变量何时初始化、何时赋值,最简单的做法是:总是使用成员初始列。

许多class中有多个构造函数,多分成员初始列的存在就会导致不必要的重复,这情况下可以将那些初始化和赋值效率差不多的变量改用赋值操作,并封装至一个函数里,供所有构造函数调用。

c++有固定的初始化顺序,先给基类初始化,再给派生类初始化,而其成员变量是按照其声明的次序初始化,thename、theaddress、thePhone,即使他们再成员初始列中以不同次序出现。故声明是要检查其次序,以免有隐晦错误(如数组大小)

不同编译单元内定义non-local static对象的初始化顺序

所谓编译单元是指产出单一目标文件的那些源码,包括源码文件和其所包含的头文件。

static对象是指其寿命是从被构造出来到程序结束为止。该对象包括global对象、namespace内的对象、文件内、class内、函数内被声明为static的对象。

函数内的static对象称为local static对象,其他均为non-local static对象。

问题:如果某个编译单元内的non-local static对象的初始化动作调用了另一个单元的non-local static对象,它所用到的这个对象可能未初始化。

接下来有一个实例

class FileSystem
{public:
    size_t numDisk();
};
extern FileSystem tfs;

class Directory
{
public:
    Directory(params);
};
Directory::Directory(params)
{
size_t disks=tfs.numDisks();//使用tfs对象
};

Directory temper(params);//临时文件

除非再调用temper之前tfs已经被初始化,否则就会使用未初始化的tfs。可如何确保tfs会先于temper初始化呢?

答案是基本不能保证。

我们可以进行一个小小的设计:将每个non-local static对象搬到自己的专属函数中,这些函数返回一个引用指向它所含的对象,然后用户调用这些函数,而不直接涉及到对象。换句话说,non-local static对象被local static对象替代了。

上述代码更改如下

class FileSystem
{public:
    size_t numDisk();
};
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}

class Directory
{
public:
    Directory(params);
};
Directory::Directory(params)
{
size_t disks=tfs().numDisks();
};

Directory& temper()
{
static Directory td;
return td;
}

不同的是,使用tfs()和temper()而不再是tfs和temper,使用函数返回的引用,而不是直接使用对象。

  • 为内置对象进行手工初始化,因为c++不保证初始化他们。
  • 构造函数最好使用成员初值列,成员初值列中的变量应与声明时一致。
  • 为免除“跨编译单元的初始化次序问题”,应用local static替换non-local static对象。

     条款五:了解c++默默编写并调用哪些函数          

如果一个空类,你没有声明,则编译器会为它声明一个拷贝构造函数、一个=重载操作符、一个析构函数。此外若没有声明任何构造函数,编译器还会声明一个默认构造函数。所有这些函数都是inline且public的。

class Empty{};

//等价于

class Empty{
public:
empty(){}
Empty(const Empty& rhs){}
~Empty(){}
Empty& operator=(const Empty& rhs){}
};

唯有这些函数被调用时,他们才会被编译器创建出来。

Empty e1;
Empty e2(e1);
e2=e1;

默认构造函数和析构函数会给编译器一个地方来放隐藏的代码,如基类的构造函数和析构函数。

编译器构造出的析构函数时非虚函数,除非其基类声明有虚函数。

如果声明一个构造函数,编译器则不再提供默认构造函数。如果自己声明的是无参或者有参构造、则编译器还是会给一个默认的拷贝构造和=运算符重载函数。

但一般只有代码合法且有意义、编译器才会构造等号运算符重载,否则编译器不会默认生成等号运算符重载。

例子如下

class NameObject
{
    public:
        NameObject(string& name,const int &value);
    private:
        string &m_name;
        const int & m_value;
};
NameObject p("per",2);
NameObject s("old",36);

p=s;

//引用对象不能被改变,所以类不会为其构造等号运算符重载函数

还有一种情况是基类将其等号运算符重载函数设为private,派生类也不会为其创建运算符重载函数。

总结:

  • 编译器可以为类创建默认构造函数、拷贝构造函数、析构函数、等号运算符重载函数

条款六:若不想使用编译器自动生成的函数,就该明确拒绝

一般如果我们不希望类支持某功能,不声明对应函数即可,然而拷贝构造函数和等号运算符重载函数当有人调用它们时,编译器会自动构造。为了阻止编译器自动构造,我们可以将其声明为private,且不予实现。

一般而言这个方法不绝对安全,因为成员函数和友元依旧可以访问。在c++11之后,我们可以将该两个函数删除.

class pthreadpool{
private:
CThreadPool(const CThreadPool &) = delete;
CThreadPool &operator=(const CThreadPool &) = delete;
};

effective c++中提出,可以将基类的拷贝构造和等号运算符重载声明为private,然后由我们继承。

总结:

为驳回编译器自动机能,我们可以将成员函数声明为private,或者利用基类,c++11以后,我们可以直接将其删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值