《编写高质量代码:改善C++程序的150个建议》读书笔记3

来源:互联网 发布:windows同步软件 编辑:程序博客网 时间:2024/06/02 19:41

0. 开始讲了一个比较有趣的事情,是关于VS编译器中经常会出现的两个汉字“烫”和“屯”。

看下面的示例:

int main() {puts((char*)malloc(100));return 0;}
运行程序的结果如下:


再看编译产生的二进制,可以看到这个二进制里面有很多的烫。

原因主要是,在VS中,栈空间在未初始化的时候默认值是0xCC,而堆在未初始化的值是0xCD,而0xCCCC在GBK编码中对应的是“烫”,0xCDCD是“屯”。


1. new/delete和new[]/delete[]配对使用。

new和delete有两种形式,一种后面有[],一种没有。后者用来申请对象数组,具体格式是:

class CLS {public:CLS() {}CLS(int i) {}};int main() {CLS *c1 = new CLS();CLS *c2 = new CLS[3];}
基本上,使用new[]只能使用默认的构造函数,比如上面的例子,如果没有CLS(){},编译就会失败。

另外,对于内置类型数组,使用delete和delete[]没有区别,不过还是建议使用delete[]

int *a = new int[10];delete[] a;


2. new有三种形态。

1) new:这个就是我们平常使用的new。

2) operator new:这个是用来单独分配内存的,它其实就是一个普通的操作符。有一个全局的,另外还可以在类中重载。

3) placement new:这个用来在已有的内存上选择执行合适的构造函数。它是C++标准库的一部分,在<new>中声明,因此使用时要#include <new>。对应的有一个placement delete。

使用单独的new实际上会调用到后面的operator new和placement new,下面是使用operator new和placement new的例子:

#include <iostream>#include <new>using std::cout;using std::endl;class CLS {public:CLS() {}CLS(int i) {}};int main() {void *a = operator new(sizeof(CLS));CLS *c = static_cast<CLS *>(a);new(a)CLS(1);return 0;}
new(a)CLS(1)就是placement new,它的声明在VS中位于vcruntime_new.h中:

#ifndef __PLACEMENT_NEW_INLINE    #define __PLACEMENT_NEW_INLINE    _Ret_notnull_ _Post_writable_byte_size_(_Size)    inline void* __CRTDECL operator new(size_t _Size, _Writable_bytes_(_Size) void* _Where) throw()    {        (void)_Size;        return _Where;    }    inline void __CRTDECL operator delete(void*, void*) throw()    {        return;    }#endif
下面是operator new的一个重载:

#include <iostream>#include <new>using std::cout;using std::endl;class CLS {public:CLS() {}CLS(int i) {cout << "CLS(int i)" << endl;}void* operator new(size_t size){cout << "operator new override" << endl;::operator new(size);}//这个也要写,不然new(a)CLS(1);报错void* operator new(size_t size, void *where){(void)size;return where;}//既然new重载了,delete最好也重载//略};int main() {void *a = operator new(sizeof(CLS));CLS *c = static_cast<CLS *>(a);new(a)CLS(1);return 0;}


3. 注意new失败的情况。

以前new失败会返回NULL,这是为了跟C语言匹配。

后来改成抛出错误。

再后来又加了一个重载的operator new版本称为nothrow new,就是把抛出异常部分包装起来并返回了NULL。

所以用来处理new失败有两种方法:

class CLS {public:CLS() {}CLS(int i) {}};int main() {//方式1CLS *c = new (std::nothrow) CLS();if (NULL == c) {return -1;}//方式2try {CLS *c2 = new CLS();}catch (const std::bad_alloc &e) {(void)e;return -1;}}
不过方式1只能保证operator new部分不出问题,但是如果之后的构造函数在执行时还是使用了没有std::nothrow版本的new,那么还会因为没有足够内存而抛出bad_alloc,这样的话后面的if(NULL == c)判断也就没有什么意义了。


4. new失败后并不会直接返回,而是与一个或多个new_handle()被执行。

new_handler的形式如下:(VS中的<new>文件)

 #if !defined(_INC_NEW) || !defined(_MSC_EXTENSIONS)// handler for operator new failurestypedef void (__CLRCALL_PURE_OR_CDECL * new_handler) (); #endif /* !defined(_INC_NEW) || !defined(_MSC_EXTENSIONS) */
new_handler大致做的事情有以下一些:

1) 尝试获取到更多的内存;

2) 使用set_new_handler安装下一个new_handler。因为operator new会在失败时执行多次,那么new_handler也可以执行多次,也一个new_handler里面安装另一个之后,下一次就会执行新安装的new_handler;

3) 到最后new_handler都没有效果,就直接装个NULL,实际上就不是卸载了new_handler;

4) 之后就抛出异常;

5) 或者直接abort()或者exit()结束程序。

另外,可以为不同的类实现自己的new_handler,这个先不深究。


5. 检测内存泄漏的一些工具,可以去了解下。

MS C-Runtime Library/BoundsChecker/Insure++/Rational Purify/Valgrind。


6. 重载operator new/delete的一些注意点。

为什么要重载?

因为系统默认的版本效率低,可能导致内存碎片化太严重;或者在某些特殊的应用下就需要自定义的版本。另外,重载后的版本还可以加入额外的功能,如检测代码中的内存错误或者获得内存使用的统计数据等。

如何实现?

1) 重载的opeartor new必须是全局函数或者类函数,如果是类函数,还必须是静态的,因为它需要独立于单个的类实例存在。(但实际上在VS2015中并不需要static,可以看上面的例子)

2) 一个全局opeartor new的例子:(heap_alloc()和CallNewHandler()还需要具体实现)

void* operator new(size_t size){if (0 == size)size = 1;//C++标准中规定,如果内存大小是0的时候也应该返回有效的内存地址void *res;for (;;) {res = heap_alloc(size);if (res)break;if (!CallNewHandler(size))break;}return res;}
3) 一个全局opeartor delete的例子:

void operator delete(void *p) {if (NULL == p) {//C++标准规定删除一个NULL指针是安全的return;}free(p);}
4) 重载opeartor new/delete的使用参数也可以变,只要保证第一个参数是size_t。


7. 使用智能指针。

这个是个需要深入研究的,这里先不展开。


8. 使用内存池来提供内存分配的效率。

0 0
原创粉丝点击