首页 > 代码库 > Effective c++(笔记)----类与函数之实现

Effective c++(笔记)----类与函数之实现

       上篇博客中集中说明了在设计一个类的时候常遇到的问题,当然博客中还夹杂着我随时想到的一些知识,发现自己写博客没很多人写的好,可能是自己语言不会组织,要么就是写的东西大家不愿意看,反正是有这方面的专业问题或者博客中有什么明显的错误和问题,大家提出来,我也好改进哈!

回归正题,这篇博客就大概的把Effective c++中类与函数这节看到的知识点做个笔记。

设计好一个类后,自己就要去实现这个类(实现类中的成员函数、友元、非成员函数等)

可能大家会遇到以下问题

1.在类的成员函数中,尽量避免返回内部数据的handles

答:起初看到这句话感觉就不是很懂,什么叫内部数据的handles,其实说白了,就是尽量在成员函数中避免返回类的指针或者引用成员。

为什么这么说呢?

原因----当成员函数返回类的指针成员或者引用成员时,可能会破坏类的抽象性,或者破环const 成员函数的const性,特别是当涉及暂时对象(个人理解就是临时对象),可能会造成悬浮指针也就是指针指向了已经释放的内存空间-------野指针

感觉上面的话好抽象,于是就拿例子来详细说明了

class String{
public:
	String(const char *value);
	~String();
	operator char * () const;
private:
	char *data;
};
String::operator char * () const
{
	return data;
}

上面的const成员函数可是类型转换运算符,它没有返回类型,没有参数。

顺便插句:c++中没有返回类型的只有构造函数、析构函数和类型转换运算符函数

这个函数实现的功能是可以将String类型转换为char*类型的

如果定义了一个常量的String对象B,如下所示

const String B = "Hello World";
char *str = B; //调用了B.operator char * ()
strcpy(str , "Hi Mom");

当调用了String类中的类型转换符,此时指针str也指向了对象B中指针data成员指向的内存空间,这样由str直接就可以改变指针data成员指向的值,但是这就违背了对象B为const的性质,所以当函数中返回类的指针成员时,对于const的成员函数会破坏对象的const特性,那么该怎么处理呢?一种比较快比较安全的方法就是针对const对象和非const对象分别写一个成员函数。如下所示

class String{
public:
	String(const char *value);
	~String();
	operator const char * () const;
	operator char * ();
private:
	char *data;
};
String::operator const char * () const
{
	return data;
}
String::operator char * ()
{
	return data;
}

这样的话const对象调用对象的const成员函数且同时返回是const char*类型,这就使其不能通过str改变其指针指向的值(这里的const是指指针指向的值为const----上篇博客详细讨论过这个问题)

对于引用也是一样的 , 如下例所示,对const和非const的分别处理

class String{
public:
	String(const char *value);
	~String();
	operator const char * () const;
	operator char * ();
	//取元素运算符重载
	const char& operator[](int index) const;
	char& operator[](int index);
private:
	char *data;
};

const char& String::operator[](int index) const
{
	return data[index];
}
char& String::operator[](int index)
{
	return data[index];
}

当成员函数非得返回类的指针成员和引用成员时,例如上述返回char&,对于内置类型不能返回char,只能返回char&,因为对于返回值是内置型的函数,修改返回值是绝对不合法的!应该对non-const和const版本的分别处理,除此之外尽量不要返回内部数据的handles。

另外是当涉及到临时对象时会造成野指针的情况,如下例所示,对上面的类再添加一个非const的成员函数

String someFamousAuthor()
{
	if(...)
	{
		return "A";
	}else if(...)
	{
		return "B";
	}else{
		return "C";
	}
}

当同样有指针指向该函数时,如

const char *pc = someFamousAuthor();

函数最后返回String对象,当该函数执行完其对象会调用析构函数,这个对象的指针成员data所指向的内存会被释放,而此时指针pc仍然指向那块内存区域,这就是指针指向了已经释放的内存区域,也就是悬浮指针的情况,------野指针

2.在c++中定义变量时,尽可能的在能给予初值时才定义,这是为什么呢?

答:如果你写的代码较多,我相信你对这样的情况深有体会,常常会有自己的变量没有用到而受到编译器的警告,这还不算什么,最可恨的是,编译器没错误,但是执行每次的结果都不同,一直查bug,很长时间过去了,才知道原来是自己定义的变量没有初始化就开始使用了,这。。。。没办法再往下说了,一定要谨记定义变量时就要给予初值,养成这个好习惯,肯定不会出错。

当然,我们也想知道里面的原因,当定义变量没有使用编译器常常会有警告,此时,你就是定义了变量分配了内存,却没使用,你这不是糟蹋内存么?!同时你这样干也影响了你程序的效率,变量的定义除了分配了对应的内存,还要调用默认构造函数,当程序结束时调用析构函数,这都浪费时间影响程序的效率。

结论-----尽量延缓变量的定义,当你真正需要用它并且在定义的时候能够给予初值时再去定义它。

3.在函数内千万不要返回局部对象的引用和返回堆空间中的对象(也就是new产生的对象)

答:这个应该很容易理解,局部对象的作用域在局部,当函数执行完后,局部对象会随之析构,如果此时你返回了局部对象的引用,引用就是别名,看到引用就要看它绑定的对象,此时局部对象已经析构,那么此时引用没有绑定的对象。上篇博客中好像也说了,对于函数返回值是对象object时,尽量不要返回对象的引用形式,因为那样效率还没有直接返回对象的高!对于内置型返回引用的效率可能还比较高。

当然,如果返回堆空间中的对象,也就是以new获得的指针所指的对象,返回这个对象时,很容易造成内存泄漏,调用这个函数时,往往就应该想也是必须想的是,调用该函数的对象应该负责删除new的内存,如果考虑不周,很容易造成内存泄漏,不要去尝试造成内存泄漏的任何情况。

4.什么时候应该在函数的前面加上inline关键字?

答:inline关键字是声明该函数为内联函数,当调用内联函数时,内联函数的代码在调用处展开,通常是函数代码量比较少的函数,但是好像现在还是不知道怎么使用,以及使用的标准。

inline函数背后的含义是对此函数的每一个调用动作都以函数代码取代之。虽然免除了调用函数的成本,但是这很明显会增加目标代码的大小,当内联函数的代码量较大时,很容易造成程序代码膨胀现象,更糟的可能产生换页行为,使程序的大部分时间都浪费在了换页上面。所以对于代码较多的函数是不合适进行内联化的。

如果你在函数前面加上了inline关键字,这并不表示,编译器一定会将此函数内联化,有可能没有内联成功也就是out of inline ,那么此时对于这个未内联成功的函数,当调用时编译器会按照普通函数调用之。

在旧规则下,对未内联成功的函数而言,当调用它时每个编译单元都会产生这个函数的静态副本,如果这个函数中有自己定义的静态局部变量,同时也会产生这个副本,也就是说在旧规则下,对于未内联成功的函数会当成static函数,甚至是当定义了指向该函数的函数指针时,也就是对这个函数取地址,此时对于每个取地址的编译单元也还是会产生这个函数的静态副本,相反,在新规则下,不论牵扯到的编译单元有多少个,只有一个未内联成功的函数副本产生出来。

对于构造函数和析构函数往往不是内联函数的最佳选择,也许你认为构造函数中什么代码也没有,正好合适内联化,但是你只看到了表面没有看到内在的运行机制,对于构造函数,它会初始化类中所有数据成员 ,如果初始化成员时的构造函数也是内联函数的话,那个该构造函数会包含好几个内联函数,再且,当这个类是派生类的时候,它不仅需要构造自己的数据成员,首先它会构造自己直接基类的数据成员,这些都是需要执行的,所以对于构造函数和析构函数而言是不适合作为内联函数的。

对于函数,在开始的时候尽量不要定义为内联,当自己确定找到了那些占重要效率地位的函数,且代码量确实很少,立即将其内联化会有助于提高自己的程序效率。

5.怎么将文件之间的编译依赖关系降至最低来提高程序重编译的效率呢?

答:首先说为什么这么干,因为如果文件之间的关系太紧密,如这个类中包含了好几个类的.h文件,那么就会导致我们改变任何一个.h文件的任何一个小的地方,就会导致整个程序重新编译,那么在编译过程就会浪费很长的时间。

其实这些都是可以避免的,有两种方法可以达到这种效果降低文件之间的依赖关系。

首先,尽量以class声明取代class定义,成为Handle class的做法

另外,使用抽象类的做法

说实话,这两种方法有些没看懂,可能是自己的基础不好,还需要再研究一下,等什么时候透彻了,将对这部分的理解和例子添加进来哈!