《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<PhoneNumber>& phones );
private:
    std::string theName;
    std::string theAddress;
    std::list<PhoneNumber> thePhones;
    int numTimesConsulted;
};

ABEntry::ABEntry( const std::string& name , const std::string& address, const std::list<PhoneNumber>& phones)
{
    theName = name;<span style="white-space:pre">		</span>// 这些都是赋值,并非初始化
    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<PhoneNumber>& phones)
:theName(name),<span style="white-space:pre">			</span>// 这些都是初始化
theAddress(address),
thePhone(phones),
numTimesConsulted(0)
{<span style="white-space:pre">				</span>// 构造函数本体不必有任何动作
}

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

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

ABEntry::ABEntry( const std::string& name , const std::string& address, const std::list<PhoneNumber>& 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********************************************

时间: 2024-11-08 09:18:06

《Effective C++ 》学习笔记——条款04的相关文章

Effective C++学习笔记 条款04:确定对象被使用前已先被初始化

一.为内置类型对象进行手工初始化,因为C++不保证初始化它们. 二.对象初始化数据成员是在进入构造函数用户编写代码前完成,要想对数据成员指定初始化值,那就必须使用初始化列表. 1 class A 2 { 3 public: 4 A(const string &str) 5 { 6 m_str = str; //m_str 的初始化是在进入构造函数中用户自定义编写代码前完成的,所以这里的m_str = str,执行的不是初始化,而是赋值 7 } 8 private: 9 string m_str;

effective c++学习笔记条款23-25

条款23:宁可用非成员,非友元函数来替代成员函数 1.非成员函数提供了更好的封装性,这个函数内不能访问类的私有成员,封装的越严密我们对类的数据就可以弹性越大的操纵,因为可见这些数据的客户越少,反之数据影响的客户也就越少. 2.c++比较自然的做法-(关系到标准库numplace的组织结构),可以把不同便捷函数放到不同Namespace去,让客户来决定要用的非成员函数功能,这是类不能提供的. 条款24:若所有参数皆需类型转换,请为此采用非成员函数. 1.如果你需要为某个函数的所有参数(包括被thi

effective c++学习笔记条款11-13

条款11: 1.令赋值运算符返回一个&,因为STL,string都是这样做的,除非你有足够好的理由不这样做. 2.处理自我赋值的方法----(1).在没有成功获取对象数据时不要删除自己的数据,避免发生异常后原对象指针是一个悬浮指针 (2).判断自我赋值的检查操作会耗费不少时间,可以用swap交换数据技术来优化---(1)形参为赋值而来,(2)形参为静态引用,多加一个函数内拷贝操作.

effective c++学习笔记条款8-10

条款7:为多态基类声明虚析构函数 1.一个基类指针接受一个派生类对象的地址时,对该指针delete,仅仅释放基类部分 2.给所有类都带上虚析构函数是个馊主意,会带有vptr指向一个函数指针数组,扩大不必要的对象大小,除非补偿vptr,否则没有移植性. 3.string类和STL不含有虚析构函数,然而一些用户 却将他们作为基类,运用   delete指向派生类的基类指针,导致错误[c++11添加了禁止派生性质],他们不适合当基类. 4,手头上没有合适的纯虚函数,但你确实需要一个抽象类,把析构函数声

effective c++学习笔记条款20-22

条款20:用引用传递代替值传递 1.尽量以引用传递来代替传值传递,前者比较高效,并且可以避免切割问题 2.以上规则不适用于内置类型,以及STL的迭代器,和函数对象 条款21:必须返回对象时,别妄想返回对象的引用 1.绝对不要返回指针和引用指向一个局部对象或者静态局部对象而有可能需要多个这样的对象,条款4已经为在单线程环境合理返回&指向一个局部静态提供了一份设计实例.(保护初始化顺序) 条款22:将成员变量声明为private 1.切记将成员变量声明为private.这可赋予客户访问数据的一致性,

effective c++学习笔记条款4-7

条款4:确定对象被使用前已经初始化 一. 变量在不同情况下可能会初始化,也可能不会初始化. 注意初始化和赋值的区别. 1.在类中内置类型不会发生隐式初始化,自定义有默认构造函数的能被默认初始化 所以在构造类时务必初始化内置类型,最好给自定义的对象显示初始化避免在函数体中赋值浪费资源. 2.内置类型在函数体内不会初始化,在函数体外自动初始化为0. 二. 1.const和引用类型必须初始化,不可能赋值 三 1.当类实在是有较多构造函数,并且总是要对一些成员数据重复初始化,可以考虑将那些“赋值和初始化

effective c++学习笔记条款17-19

条款17:以独立语句将New对象放置入智能指针. 1.以独立语句将newed对象放置入智能指针内,如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露. void name(shared_ptr<管理对象类型>(new 管理对象类型),其它函数)),New被分配内存不一定马上放入管理对象,因为有其它函数干扰,这不是独立语句. 条款18:让接口容易被正确使用,不易被误用. 1.好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质. 2.“促进正确使用”的办法包括接

effective c++学习笔记条款35-37

#include<iostream> using namespace std; class A { public: void asd() { pri(); } private: /*virtual*/ void pri() { cout << "基类函数" << endl; } }; class B :public A { private: void pri() /*override*/ { cout << "派生类函数&quo

effective c++学习笔记条款29-31

条款29:为异常安全而努力是值得的[回顾] 1.异常安全函数即使发生异常也不会泄露资源或允许任何数据结构败坏,这样的函数分为3种可能的保证:基本型,强烈型,不抛异常型 2.“强烈保证”往往能通过copying and swap 来实现出来,但并非所有函数都可实现或者具备现实意义. 3.函数提供的“异常安全保证”通常最高只等于其所调用的各个函数的“异常安全中”的最弱者.