您的位置:首页 > 编程语言 > C语言/C++

Effective & More Effective C++总结

2010-01-12 15:16 288 查看
一、说明。

1、E指的是《Effective C++ 第二版》

2、M指的是《More Effective C++ 第二版》

二、总结

1、如果需要定制自己的operator new(通常是在需要分配大量的小对象,而且对性能要求特别敏感的程序里),具体的使用准则请参看 E7/E8/E9/E10。

(1)、全局的operator new 在<new>里定义,可以通过::operator new()调用。它的伪代如下

void * operator new(size_t size)        // operator new还可能有其它参数
{

if (size == 0) {                      // 处理0字节请求时,
size = 1;                           // 把它当作1个字节请求来处理
}
while (1) {
分配size字节内存;

if (分配成功)
return (指向内存的指针);

// 分配不成功,找出当前出错处理函数
new_handler globalhandler = set_new_handler(0);
set_new_handler(globalhandler);

if (globalhandler) (*globalhandler)();
else throw std::bad_alloc();
}
}


它将循环执行内存分配,跳出循环的唯一办法是内存分配成功或出错处理函数完成。

得到了更多的可用内存;

安装了一个新的new-handler(出错处理函数);使用set_new_handler()函数设置。默认情况下new_handler将为NULL,此时operator new将自动抛出std::bad_alloc异常。如果设置了一个new handler函数,但又分配足够的内存,或者不退出,或者不抛出异常,那operator new 将一直循环执行。

卸除了new-handler;

抛出了一个std::bad_alloc或其派生类型的异常;

或者返回失败。

默认情况下,new handler是NULL,operator new 将不会循环,而是抛出std::bad_alloc退出。如果你通过调用set_new_handler()函数设置了一个异常处理函数,在你的new handler函数里,你必须要遵循上面说的5个条件之一或者更多。不然operator new将一直循环的调用你的new handler函数。

2、为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符,或者如果你不需要此两个函数,那么就要把他们声明为private。

具体的使用准则请参看 E11。

因为默认的类里自动为你生成了这两个函数,而且是使用浅复制的形式。所以你将有可能遇到以下情况:

内存泄漏(即分配的内存没有指针指向,而又还没有删除)

多重删除

当实现赋值操作符时,准则是:一定要返回*this的引用 E15/E16/E17

否则会出现:

(1)无法连续赋值(因为要保持与普通=号的职责)

正确的标准写法是:

string& string::operator=(const string& rhs)
{
if (this == &rhs) return *this;

base::operator=(rhs);    // 调用this->base::operator=
//但如果基类赋值运算符是编译器生成的,有些编译器会拒绝这种对于基类赋值运算符的调
//用E45。为了适应这种编译器,必须这样实现
//static_cast<base&>(*this) = rhs;      // 对*this的base部分

y = rhs.y;

return *this;

}


3、基类要有虚析构函数。E14

(1)否则将会导致:

指针指向的子类无法调用正确的析构函数(当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。)

(2)一般的做法是:当不需要要实例化基类时,最好把析构函数声明为纯虚析构函数(当然析构函数要有实现)。

(3)把非基类的析构函数声明为虚析构函数,会导致两种问题:第一是性能问题,因为有了虚拟函数表;第二是与其他语言之间的交互,此问题一般比较少遇到。

4、尽量使用初始化而不要在构造函数里赋值

const和引用数据成员只能用初始化,不能被赋值

性能上考虑,如果是赋值,那么将会有两次的赋值过程,一次是在类成员声明里,一次是在赋值里;而初始化只有一次。

5、参数的实质

例子:int function(int a,string b,string& c,string*d);

function(1,"b","c","d");

(1)、首先生成一个临时的对象。

对于第1个参数,是生成一个int a,然后把int值1赋值给a。所以改变a的值没有办法改变值1。

对于第2个参数,把"b"的值通过构造函数构造一个对象b,所以改变b的值也没有办法改变值"b";

对于第3个参数,生成一个string&对象,此引用的对象类似与指针,把"c"的地址传给string&对象,所以改变c的内容,将改变"c"的内容。但无法再次改变c的指向,因为c是一个引用。

对于第4个参数,生成一个string*对象,任何对象的指针的大小在一般的系统下都是4个字节大小,然后把"d"的地址传递给d,

所以改变d的内容将改变"d"的内容。但如果把d再赋值另外的地址,因为已经指向了另外一个地址,d对象的内容的改变将无法影响"d"。

结论:任何形式的参数传递都是值传递,但值的类型由参数的类型决定。或者是个对象,或者是个指针,或者是个引用。

6、尽量使用const;尽量传引用,不传值;函数重载和默认缺省参数要仔细考虑;不要对指针和数字类型重载;

7、类设计要点。

(1)C++编译器自动生成缺省构造函数、拷贝构造函数、析构函数、赋值运算符、取址运算符。如果不想使用这些默认定义,应该重定义他们或者直接使用private屏蔽。

定义一个类:

class Empty{};

等价于

class Empty {
public:
Empty();                        // 缺省构造函数
Empty(const Empty& rhs);        // 拷贝构造函数

~Empty();                       // 析构函数 ---- 是否
// 为虚函数看下文说明
Empty&
operator=(const Empty& rhs);    // 赋值运算符

Empty* operator&();             // 取址运算符
const Empty* operator&() const;
};


(2)operator= 的设计。E15/E16/E17

检查给自己赋值的情况

对所有数据成员赋值

返回*this的引用

class_a& class_ar<t>::operator=(const namedptr<t>& rhs)
{
if (this == &rhs)
return *this;              // 见条款17

// assign to all data members
name = rhs.name;             // 给name赋值

*ptr = *rhs.ptr;             // 对于ptr,赋的值是指针所指的值,
// 不是指针本身

return *this;                // 见条款15
}


(3)、公有继承、存虚函数、虚函数、非虚函数的意义

纯虚函数意味着仅仅继承函数的接口。如果类C声明了一个纯虚函数mf,C的子类必须继承mf的接口,C的具体子类必须为之提供它们自己的实现。见E36。
简单虚函数意味着继承函数的接口加上一个缺省实现。如果类C声明了一个简单(非纯)虚函数mf,C的子类必须继承mf的接口;如果需要的话,还可以继承一个缺省实现。见E36。
非虚函数意味着继承函数的接口加上一个强制实现。如果类C声明了一个非虚函数mf,C的子类必须同时继承mf的接口和实现。实际上,mf定义了C的 "特殊性上的不变性"。见E36。

决不要重新定义继承而来的非虚函数 E37

虚函数的使用要特别注意,因为子类都默认继承了实现,所以虚函数的实现子类必须是要一致的。E36

8、非局部静态对象初始化。E47.

使用函数内定义的技巧保证初始化。

class FileSystem { ... };            // 同前
FileSystem& theFileSystem()          // 这个函数代替了
{                                    // theFileSystem对象

static FileSystem tfs;             // 定义和初始化
// 局部静态对象
// (tfs = "the file system")

return tfs;                        // 返回它的引用
}


未完待续~~~~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: