《Effective C++ 》学习笔记——条款04

***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************


 一、 Accustoming Yourself to C++


Rules 4: Make sure that objects are initialized before they're used

条款4:确定对象被使用前已先被初始化


一、原因:

关于”将对象初始化“这事,C++似乎反复无常,因为你如果这么写:

int x;

在某些情况下,它会被初始化(为0),有时候不会,这乍一看不重要,其实对于灵活的C++,这往往会扮演致命的角色。而且在不同情况下,这个规则也不同。

通常如果使用C part of C++ 而且初始化可能招致运行期成本,那么就不保证发生初始化,但若进入non-C parts of C++ 规则就会有些变化。

典型的例子就是,来自C part of C++ 的array 不保证它的内容被初始化,而来自STL part of C++的 vector 却有这个保证。


二、解决:

如此解决这个问题呢?就如同题目所讲,永远在使用对象前先将它初始化。

1.初始化

对于内置类型,初始化可以通过手工,例如:

 int x = 0 ;

const char*  text = "A C-style string";

读取流的方式初始化:

double d;

std::cin>>d;

而对于非内置类型,初始化的责任就在 constructors(构造函数)身上,规则很简单:确保每一个构造函数都将对象的每一个成员初始化。

2.别混淆了 assignment(赋值) 和 initialization(初始化)

同样看例子:这是一个用来表现通讯薄的class,其构造函数如下

class PhoneNumber  {...};
class ABEntry  {
public:
    ABEntry( const std::string& name, const std::string& address, const std::list& phones );
private:
    std::string theName;
    std::string theAddress;
    std::list thePhones;
    int numTimesConsulted;
};

ABEntry::ABEntry( const std::string& name , const std::string& address, const std::list& phones)
{
    theName = name;		// 这些都是赋值,并非初始化
    theAddress = address;
    thePhones = phones;
    numTimesConsulted = 0;
}

正如注释上所说,ABEntry函数里的内容都是赋值行为,并非是初始化行为,因为C++规定了,对象的成员变量初始化动作发生在进入构造函数本体之前。更准确的来说是发生于这些成员的default构造函数被自动调用之时。

对于构造函数比较好的写法是,使用所谓的 member initialization list(成员初值列)替换赋值动作:

ABEntry::ABEntry( const std::string& name , const std::string& address, const std::list& phones)
:theName(name),			// 这些都是初始化
theAddress(address),
thePhone(phones),
numTimesConsulted(0)
{				// 构造函数本体不必有任何动作
}


这两个虽然结果相同,但是,后者效率较高,对大多数类型而言,比起先调用default构造函数然后再调用 copy assignment 操作符,单只调用一次copy构造函数是比较高效的。

对于内置类型,比如例子中的 numTimesConsulted来说,初始化和赋值的效率基本相同,但是为了一致性,最好也通过成员初值列来进行初始化,甚至如果你想要default构造一个成员变量,都可用指定 nothing(无物)作为初始化实参即可,比如:

ABEntry::ABEntry( const std::string& name , const std::string& address, const std::list& phones)
:theName(),			
theAddress(),
thePhone(),
numTimesConsulted(0)
{				
}

规则:

<1> 编译器会为 user-defined types (用户自定义类型) 之成员变量自动调用default构造函数——如果那些成员变量在”成员初值列“中没有被指定初值的话。所以,最好在初值列中列出所有成员变量,可以无须初值,这样可以记住都有哪些成员变量,防止遗漏。

<2>有些情况下即使面对的成员变量属于内置类型,也一定要使用初值列,如果成员变量是const 或 reference,它们就一定要初值,不能被赋值。

这两个规则总结下来,就是——总是使用成员初值列,列出所有成员变量。


三、针对一些现象的解决

1.现象: 许多classes 拥有多个构造函数,每个构造函数有自己的成员初值列。这样就会导致很多的重复动作。

解决:这种情况就可以合理的在初值列中遗漏那些 赋值操作和初始化 一样好的成员变量,把它们用赋值操作,并将这些操作移到一个private函数,供所有构造函数使用,这种做法尤其适用在:成员变量的初值是由文件或数据库读入的时候。当然相对于这种的伪·初始化,还是用成员初值列的 真·初始化 更可取。

2.现象:C++ 有着十分固定的 成员初始化次序,次序就是  base classes 更早于其 derived classes 被初始化。classes成员变量总是以其声明的顺序次序初始化。

解决:当在成员初值列中条列各个成员时,最好总是以其声明次序为次序,这样就避免了一些隐藏的错误,比如初始化数组前要指定数组大小,因此代表大小的变量要先于数组初始化。

3.

①现象:继承与第二个现象,就是——non-local static对象(不同编译单元内定义)的初始化次序

②解释:首先static 对象,它的寿命从被构造出来直到程序结束为止,这样 stack 和 heap-based 对象都被排除,这样的对象包括 global对象,定义于namespace作用域内的对象、在classes内、在函数内、以及在file作用域内被声明为static的对象。(函数内的static对象成为local static 对象,其他static对象成为 non-local static 对象)

其次 编译单元,这是指 产出单一 single object file(目标文件)的那些源码。基本上就是 单一源码文件加上其所含入的头文件。

而C++ 对  定义于不同编译单元内的non-local static 对象的初始化次序并无明确定义。

③例子:这种例子也很明显,就是 A 中 extern了B,并且调用了B的对象,但是,可能调用A的对象的时候,B并没有初始化,因此会导致错误,因为调用A时,调用了B的对象,但是B没有初始化。

④原因:原因也是非常简单,因为决定这个次序太难,甚至于无解的地步。

⑤解决:将每一个non-local static对象搬到自己的专属函数内(该对象在此函数声明为static)这些函数返回一个reference指向它所含的对象,这样 non-local static 对象被 local static对象替换了。

⑥原理:它的基础是:C++保证,函数内的local static 对象会在”该函数被调用期间“”首次遇上该对象定义式“时被初始化。而且这样做以后,如果在执行期间,没有调用 non-local static 的仿真函数,就绝对不会引发构造和析构成本,这也是原来的non-local static没有的好处。



四 Please Remember

<1>为内置型对象进行手工初始化,因为C++不保证初始化它们。

<2>构造函数最好用 成员初值列 ,而不要在构造函数中使用赋值操作。初值列列出的成员变量,应该按照其在类内的声明次序进行排序。

<3>为免除”跨编译单元之初始化次序“问题,请以local static对象替换 non-local static对象



第一章结束,

最近一段时间真的好忙啊 +_+..

各种事找上来,Effective C++ 这本书,其实已经看了不少,但是一直没时间更新笔记,

cocos2d-x 的 三消——万圣大作战 也做完了,接下来又要开始新的一个开发,

算法重拾系列,也要更新。。

化压力为动力,继续冲下去吧!



***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************

你可能感兴趣的:(Effective,C++,Effective,C++,学习笔记,条款04)