随笔-341  评论-2670  文章-0  trackbacks-0
    复杂的东西写多了,如今写点简单的好了。由于功能上的需要,Vczh Library++3.0被我搞得很离谱。为了开发维护的遍历、减少粗心犯下的错误以及增强单元测试、回归测试和测试工具,因此记录下一些开发上的小技巧,以便抛砖引玉,造福他人。欢迎高手来喷,菜鸟膜拜。

    C++实谓各种语言中的软肋,功能强大,陷阱更强大。当然我认为一门语言用得不好完全是程序员的责任,不过因为C++涉及到的概念实在是太多,想用好实在也不是一件容易的事情。C++开发的时候总是会遇到各种各样的问题,其中最严重的无非是内存相关的。C语言由于结构简单,内存处理起来虽然不得力,但总的来说惯用法已经深入人心,因此也不会造成什么很难发现的错误。C++就不一样了。有了虚函数、构造函数、析构函数、复制构造函数和operator=重载之后,还是有很多人喜欢把一个类直接写进文件流,或者拿来memset,代码一团乱麻,不知悔改也。但是不能因此因噎废食,就像某人因为C++带来的心智问题太多,自己搞不定,自己团队也搞不定,就说C++不好一样。

    因此第一篇文章主要针对内存来讲。我们处理内存,第一件事就是不要有内存泄露。内存泄露不能等到测试的时候,通过长时间运行程序并观察任务管理器的方法来做,这显然已经晚了。幸好Visual C++给了我们一个十分好用的工具:_CrtDumpMemoryLeaks函数。这个函数会在Debug模式下往Visual Studio的output窗口打印出那个时候你new(malloc)了但是还没delete(free)的所有内存块的地址、长度、前N个字节的内容和其他信息。怎么做呢?其实很简单:
 1 #define _CRTDBG_MAP_ALLOC
 2 #include <stdlib.h>
 3 #include <crtdbg.h>
 4 #include <windows.h>
 5 
 6 int wmain(vint argc , wchar_t* args[])
 7 {
 8     // 这里运行程序,并在下面的函数调用之前delete掉所有new的东西
 9     _CrtDumpMemoryLeaks();
10     return 0;
11 }

    我们只需要在注释的地方完成我们程序的功能,然后确信自己已经delete掉所有应该delete的东西,最后_CrtDumpMemoryLeaks()函数调用的时候就可以打印出没被delete的东西了。这个方法十分神奇,因为你只需要在main函数所在的cpp文件这么#include一下,所有的cpp文件里面的new都会受到监视,跟平常所用的用宏把new给换掉的这种破方法截然不同。如果你使用了全局变量的话也要小心,因为全局变量的析构函数是在main函数结束之后才执行的,因此如果在全局变量的析构函数里面delete的东西仍然会被_CrtDumpMemoryLeaks函数当成泄露掉的资源对待。当然本人认为全局变量可以用,但是全局变量的赋值必须在main里面做,释放也是,除非那个全局变量的构造函数没有申请任何内存,所以这也是一个很好的检查方法。

    不过上面也仅仅是一个告诉你有没有内存泄漏的方法罢了。那么如何避免内存泄露呢?当然在设计一些性能要求没有比操作系统更加严格的程序的时候,可以使用以下方法:
    1、如果构造函数new了一个对象并使用成员指针变量保存的话,那么必须在析构函数delete它,并且不能有为了某些便利而将这个对象的所有权转让出去的事情发生。
    2、在能使用shared_ptr的时候,尽量使用shared_ptr。shared_ptr只要你不发生循环引用,那么这个东西可以安全地互相传递、随便你放在什么容器里面添加删除、你想放哪里就放在哪里,再也不用考虑这个对象的生命周期问题了。
    3、不要在有构造函数和析构函数的对象上使用memset(或者memcpy)。如果一个对象需要memset,那么在该对象的构造函数里面memset自己。如果你需要memset一个对象数组,那也在该对象的构造函数里面memset自己。如果你需要memset一个没有构造函数的复杂对象,那么请为他添加一个构造函数,除非那是别人的API提供的东西
    4、如果一个对象是继承了其他东西,或者某些成员被标记了virtual的话,绝对不要memset。对象是独立的,也就是说父类内部结构的演变不需要对子类负责。哪天父类里面加了一个string成员,被子类一memset,就欲哭无泪了。
    5、如果需要为一个对象定义构造函数,那么连复制构造函数、operator=重载和析构函数都全部写全。如果不想写复制构造函数和operator=的话,那么用一个空的实现写在private里面,确保任何试图调用这些函数的代码都出现编译错误。
    6、如果你实在很喜欢C语言的话,那麻烦换一个只支持C不支持C++的编译器,全面杜绝因为误用了C++而导致你的C坏掉的情况出现。

    什么是循环引用呢?如果两个对象互相使用一个shared_ptr成员变量直接或者间接指向对方的话,就是循环引用了。在这种情况下引用计数会失效,因为就算外边的shared_ptr全释放光了,引用计数也不会是0的。

    今天就说到这里了,过几天我高兴的话再写一篇续集,如果我持续高兴的话呢……嗯嗯……。
posted on 2010-06-22 05:16 陈梓瀚(vczh) 阅读(38026) 评论(32)  编辑 收藏 引用 所属分类: C++实用技巧

评论:
# re: C++实用技巧(一) 2010-06-22 05:23 | 小时候可靓了
沙发,占了再看!  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-22 06:23 | 空明流转
膜拜  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-22 06:24 | 空明流转

6、如果你实在很喜欢C语言的话,那麻烦换一个只支持C不支持C++的编译器,全面杜绝因为误用了C++而导致你的C坏掉的情况出现。

对于主流C++编译器来说,换一下编译选项就可以了。  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-22 07:07 | HoneyCat
就是 加了virtual的 好像 似乎 平时没怎么注意
不过还好 没那么用过  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-22 07:40 | chaogu
十分高产....  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-22 08:09 | 小时候可靓了
@HoneyCat
置0会把虚表指针也置为0(NULL),你的虚函数还咋访问?  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-22 08:28 | DJ
在类里面,如果有个static的stl的vector,就算在_CrtDumpMemoryLeaks()之前clear了那个vector,还是会报内存泄漏...
我之前写一个东西就是这样,把全部new查了一遍都没发现问题,后面才发现是static成员变量的原因...  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-22 18:10 | zuhd
@DJ
对于全局变量,我个人觉得还是在正确的地方,去手工调用它的析构函数,类似于SAFE_RELEASE()的做法
  回复  更多评论
  
# re: C++实用技巧(一)[未登录] 2010-06-22 22:18 | jans2002
拜一下。这种科普性的内容对于新手很受用。  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-22 22:26 | 陈梓瀚(vczh)
@DJ
static成员也应该视为全局变量  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-23 07:11 | lhking
明天用用  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-23 09:13 | 欲三更
我倾向更激进的策略:
1.尽量少用指针指向的数组,用的话尽量用对象把他们包装起来。
2.调试时期的代码中检查每一个指针的合法性,不合法立刻崩溃。
3.多线程程序中如果一个对象包装了带锁的资源,那这个对象要么是全局的,要么只能有一个指定的线程来delete,而这个线程不使用相关资源。
4.少new,少delete,最好能别new就别new。
。。。  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-23 20:20 | DJ
@陈梓瀚(vczh)
的确是,不过与直接的全局变量相比,static显得有些隐晦.面向新手的文章,还是提一下好点~不然真遇到了就浪费时间了

@zuhd
SAFE_RELEASE()是删除指针的吧.如果vector不是指针,显示调用~vector的的话,这样main结束后就调用了两次vector的析构函数了,不知道会不会有些啥未定义行为.  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-23 20:37 | runcoder
3、不要在有构造函数和析构函数的对象上使用memset(或者memcpy)。如果一个对象需要memset,那么在该对象的构造函数里面memset 自己。如果你需要memset一个对象数组,那也在该对象的构造函数里面memset自己。如果你需要memset一个没有构造函数的复杂对象,那么请为他添加一个构造函数,除非那是别人的API提供的东西。
这个地方的东西矛盾了啊  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-23 22:02 | 陈梓瀚(vczh)
@runcoder
其实我的意思是,如果代码你有权限改,那就应该加上构造函数而不是memset。当然并不是所有代码你都有办法改的,譬如说winapi……  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-24 01:50 | 陈昱(CY)
哪本书说memset、memcpy之类的函数是C语言的,用了C++,就尽量不要用这些函数,除了用在2进制buffer上  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-24 19:54 | Benjamin
memset、memcpy等在华为等大公司的编程规范中是禁止使用的。  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-25 10:35 | paul_simon
记忆中,在复制构造函数和operator=()复制构造函数中用使用
if(this == &item)
this = 0;
防止指针的循环使用吧。。。。。。  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-25 20:10 | 陈梓瀚(vczh)
@paul_simon
this=0;是语法错误。  回复  更多评论
  
# re: C++实用技巧(一) 2010-06-26 04:11 | paul_simon
@陈梓瀚(vczh)
是啊?!  回复  更多评论
  
# re: C++实用技巧(一) 2010-07-01 00:25 |
assert
operator new
operator delete
pool_allocator
object_allocator
shared_ptr
multi thread
different heap alloc
以上都是内存相关的诀窍和问题,突然发现C++真是个变态的语言,总是在这些问题上纠缠不休,但让人绝望的是,让所有高级语言黯然失色的设计模式应用,以及与C并驾齐驱的执行效率,都让我等对它爱不释手...  回复  更多评论
  
# re: C++实用技巧(一) 2010-07-01 01:07 | 陈梓瀚(vczh)
@酿
所以请使用你列表里面的东西,尽量避免指针。  回复  更多评论
  
# re: C++实用技巧(一) 2010-08-26 04:02 | MissFuture
呵呵,确实有道理,很实用  回复  更多评论
  
# re: C++实用技巧(一) 2011-07-20 05:28 | 含笑傲月
我是菜鸟,特此膜拜。呵呵  回复  更多评论
  
# re: C++实用技巧(一) 2011-08-17 07:21 | barry
受益匪浅。只要用指针,一定要小心再小心。  回复  更多评论
  
# re: C++实用技巧(一) 2012-03-21 01:51 | 王建强
小菜鸟 想学习  回复  更多评论
  
# re: C++实用技巧(一) 2012-05-24 20:43 | 叫我老王吧
某人莫非是指云风?  回复  更多评论
  
# re: C++实用技巧(一) 2013-06-15 16:54 | Sniffer
@Benjamin
那他们使用什么方法初始化的呢?比如字符数组?  回复  更多评论
  
# re: C++实用技巧(一)[未登录] 2013-06-16 22:23 | 陈梓瀚(vczh)
@Sniffer
譬如说wchar_t* a[] = L"vczh";  回复  更多评论
  
# re: C++实用技巧(一) 2014-03-20 01:23 | 汪良
你好!我是从 C++ Primer 那本书上看到您的,看到你的微博里面有很多C++的东西,但是关注不了,您能设置一下吗?  回复  更多评论
  
# re: C++实用技巧(一) 2014-06-28 18:00 | Endless
看完了4篇文章,每篇都学到了知识,感谢!  回复  更多评论
  
# re: C++实用技巧(一) 2014-10-30 23:56 | 迦仑
假如当年我刚读大学就认识你该多好
膜拜轮子哥  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理