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

笔记3


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() {
	//方式1
	CLS *c = new (std::nothrow) CLS();
	if (NULL == c) {
		return -1;
	}
	//方式2
	try {
		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 failures
typedef 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值