C++编程规范-华为-Word文档-已整理过-07-my04 下载本文

内容发布更新时间 : 2024/11/9 1:51:35星期一 下面是文章的全部内容请认真阅读。

正确的做法是在【3】之前加上:

for(int j = i ; j < n; ++j) {

free(namelist[i]); }

7. 规则6.6 不要返回局部对象指针

说明:局部对象在定义点构造,在同一作用域结束时立即被销毁。 示例:

char* GetParameter () {

CDBConnect DBConnect; //………………..

return DBConnect.GetString(\}

由于对象DBConnect已经析构,对应的指针已经被释放,从而后续访问非法内存,导致系统coredump。

8. 规则6.7 不要强制关闭线程

说明:线程被强制关闭,导致线程内部资源泄漏。用事件或信号量通知线程,确保线程调用自身的退出函数。线程死锁需要强制关闭的情况除外。

示例:强制关闭线程,导致线程资源泄漏。

CShakeHand::CShakeHand() {

m_hdShakeThreadrecv = CreateThread(NULL, 0,

(LPTHREAD_START_ROUTINE)ThreadProc_ShakeHands, this, NULL, &m_ulShakeThreadID); }

CShakeHand::~CShakeHand() {

TerminateThread(m_hdShakeThreadrecv, 0); //强制关闭 CloseHandle(m_hdShakeThreadrecv); }

9. 建议6.1 使用new, delete的封装方式来分配与释放内存

说明:推荐使用如下宏,可以在一定程度上避免使用空指针,野指针的问题。

#define HW_NEW(var, classname) \\ do { \\

try \\ { \\

var = new classname; \\ } \\ catch (...) \\ { \\

var = NULL; \\ } \\ break; \\ } while(0)

//(1)该宏会将var置为NULL, 所以调用该宏之后, 不再需要置var为NULL //(2) HW_DELETE宏与NEW对应, 用来释放由HW_NEW分配的对象

// 注意: 如果以数组方式分配对象(见对HW_NEW的描述), 则必须使用宏HW_DELETE_A // 来释放, 否则可能导致问题,参见:规则6.3 #define HW_DELETE(var) \\ do \\ { \\

if (var != NULL) \\ { \\

delete var; \\ var = NULL; \\ } \\ break; \\

} while(NULL == var)

//(1)这个宏用来删除一个由HW_NEW分配的数组, 删除之后也会将var置为NULL #define HW_DELETE_A(var) \\ do \\ { \\

if (var != NULL) \\ { \\

delete []var; \\ var = NULL; \\ } \\ break; \\

} while(NULL == var)

直接使用HW_DELETE,HW_DELETE_A宏来释放指针内存空间,就不会出现遗忘将指针置为NULL了。

10. 建议6.2 避免在不同的模块中分配和释放内存

说明:在一个模块中分配内存,却在另一个模块中释放它,会使这两个模块之间产生远距离的依赖,使程序变得脆弱。

模块在C++是一个不清晰的概念,小到一个类,大到一个库。如果在不同的类之间分配、释放内存,需要考虑两个类的初始化、销毁顺序;如果在不同的库之间分配、释放内存,需要考虑两个库的加载或卸载顺序。这种远距离的依赖,容易导致遗漏和重复操作,引发严重问题。

有时,在通信机制下,两个实体(如线程)之间交换数据或消息,考虑到程序执行效率,不会采用拷贝的方式交换数据,而是通过指针交换数据,仍然会在不同位置分配、释放内存。这种情况下,只有数据交换成功以后,才会由对方负责释放,否则应遵循“谁申请、谁释放”的原则。为了降低处理复杂性,可以适当地采用RAII或智能指针。

11. 建议6.3 使用 RAII 特性来帮助追踪动态分配

说明:RAII是“资源获取就是初始化”的缩语(Resource Acquisition Is Initialization),是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

RAII 的一般做法是这样的:在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。这种做法有两大好处:

我们不需要显式地释放资源。

对象所需的资源在其生命期内始终保持有效。这样,就不必检查资源有效性的问题,可以简化逻辑、提高效率。

C++类库的智能指针就是其中之一:

auto_ptr是标准C++库提供的一种模板类,使用指针进行初始化,其访问方式也和指针一样。在auto_ptr退出作用域时,所指对象能被隐式的自动删除。这样可以象使用普通指针一样使用auto_ptr,而不用考虑释放问题。注意:auto_ptr的复制会造成它本身的修改,原有的auto_ptr将不再指向任何对象,而由新的auto_ptr接管对象内存,并负责自动删除。因此auto_ptr复制后不能再使用,且不能复制const auto_ptr。

boost库中提供了一种新型的智能指针shared_ptr,它解决了多个指针间共享对象所有权的问题,同时也满足容器对元素的要求,因而可以安全地放入容器中。shared_ptr解决了auto_ptr移动语义的破坏性。

关于auto_ptr与shared_ptr使用请参考C++标准库的相关书籍。 示例:使用RAII不需要显式地释放互斥资源。

class My_scope_lock { public:

My_scope_lock(LockType& _lock):m_lock(_lock) {

m_lock.occupy(); }

~My_scope_lock() {

m_lock.relase(); } protected:

LockType m_lock; }

bool class Data::Update() {

My_scope_lock l_lock(m_mutex_lock); if() {

return false; } else {

//execute } return true; }

1.8 C++ 编程规范 7 异常与错误处理

资料来源:

http://blog.csdn.net/xiyoulele/article/details/7987152

分类: C/C++ 编码规范 2012-09-17 12:57 356人阅读 评论(0) 收藏 举报 编程c++serviceexception语言input

1.8.1 7.1 异常

异常是C++语言的一个强大特性,在正确使用之前需要深入了解,以及使用异常代码的上下文。

1. 原则7.1 减少不必要的异常

说明:异常对编码技能要求更高,使用中容易出错,首先从安全性角度考虑,尽量少用或者不用异常。 (1). 相比返回错误,异常的优点:

A. 异常可以集中捕捉,错误检测与算法处理相分离,算法逻辑更清晰;而返回错误在每个返回点都 B. 要进行检测与错误处理,代码逻辑分散。

C. 异常的约束更强,用户不能忽略抛出的异常,否则程序默认会被终止,而返回错误则可能被忽略。 (2). 异常的缺点也很明显:

A. 必须检查所有调用点是否可能抛出异常,在抛出后必须正确处理状态和资源变量等,否则可能导致对象状态不正确或者资源泄露等。例如:如果f()依次调用了g()和h(),h抛出被f捕获的异常,g就要当心了,避免资源泄露。

B. 必须清楚可能抛出的所有异常,并在合适的地方捕捉,如果遗漏通常会导致程序被终止。 C. 使用异常很难评估程序的控制流,代码很难调试。 D. 目标文件变大,编译时间延长,性能下降。

E. 若对异常缺乏充分理解,可能会在不恰当的时候抛出异常, 或在不安全的地方从异常中恢复。 (3). 适用异常的几个场景:

A. 出现“不应该出现的”失败,且不能被忽略必须处理,比如分配内存失败。 B. 上层应用决定如何处理在底层嵌套函数中“不可能出现的”失败。 C. 错误码难以通过函数的返回值或参数返回,比如流。

D. 许多第三方C++库使用异常,必须在系统边界与第三方C++库结合处使用异常便于跟这些库集成。 E. 在测试框架中使用异常很方便。

2. 规则7.1 构造和析构函数不能抛出异常

说明:如果构造和析构函数执行失败则无法安全地撤销和回滚,故这些函数不能向外抛出异常。 为了降低复杂性,建议在这类函数中实现最简单的逻辑。

3. 规则7.2 通过传值的方式抛出,通过引用的方式捕获

说明:抛出异常时,如果抛出指针,谁释放指针就成为问题。捕捉时如果是传值,会存在拷贝,拷贝可能不成功(比如异常是由于内存耗尽造成的),而且拷贝得不到派生类对象,因为在拷贝时,派生类对象会被切片成为基类对象。

4. 规则7.3 确保抛出的异常一定能被捕捉到

说明:异常未被捕捉到,系统的默认行为是终止程序运行,所以要确保程序产生的异常都能被捕捉。

5. 规则7.4 确保异常发生后资源不泄漏

说明:异常发生后,当前代码执行序列被打断,需要查看分配的内存、文件和内核句柄等资源是否正确释放,避免资源泄漏,尤其每个可能的返回点是否正确释放资源。

示例:如下代码存在内存泄漏

int PortalTransformer::transRLS {

RLS_Service* service = NULL; NEW( service, RLS_Service );

parser->adoptDocument();//失败时会抛异常 //....

delete service; service =NULL; return 0; }

调用adoptDocument出现的异常没有在函数transRLS里面被捕获,而是在父函数里面捕获了异常的派

生类。如果发生异常,则NEW( service, RLS_Service )分配的内存泄漏。

解决方案:在函数transRLS里面捕获adoptDocument的异常,如果发生异常,则删除指针service。

6. 规则7.5 独立编译模块或子系统的外部接口禁止抛异常

说明:异常处理没有普遍通用的二进制标准,所以不允许跨模块抛异常。

1.8.2 7.2 错误处理策略

1. 原则7.2 建立合理的错误处理策略

说明:这里所说的错误指运行时错误,并非模块内部的编程和设计错误。模块内部的编程和设计错误应该通过断言标记。

在设计早期确定错误处理策略,包括:鉴别,严重程度,错误检查,错误处理,错误传递,错误报告方案。

错误鉴别:对每个实体(函数、类、模块),记录该实体内部和外部的不变式、前置条件、后置条件以及它支持的错误安全性保证。

错误严重程度:对于每个错误,标明严重级别。

错误检查:对于每个错误,记载哪些代码负责检查它。 错误处理:对于每个错误,标明负责处理它的代码。 错误报告:对于每个错误,标明合适的报告方法。

错误传递:对每个模块,标明使用什么编程机制传递错误,如C++异常、CORBA异常、返回值。 错误处理策略应该只在模块边界改变。如果模块内外所使用的策略不同,则所有模块入口函数都要直接负责由内到外的策略转换。例如,在一个内部使用C++异常,但提供C语言的API边界的模块中,所有C语言的API必须用catch(?)捕获所有异常并将其转换为错误代码。

2. 原则7.3 离错误最近的地方处理错误或转换错误

说明:当函数检查到一个自己无法解决的错误,而且会使函数无法继续执行的时候,就应该报告错误。 如果缺乏处理的上下文,应该向上传播错误。

3. 规则7.6 错误发生时,至少确保符合基本保证;对于事务处理,至少符合强保证;对于原子操作,符合无错误保证

说明:基本保证是指访问对象时的状态都是正确的;强保证是对基本保证的增强,不仅要状态正确,而且当失败时状态要回滚到操作前的状态,要么成功要么什么都不做;无错误保证是不能出现失败。

编码中严格遵循此原则,会极大提升程序的健壮性。

符合基本保证的代码示例:如下代码是解析输入流到对象,流抛出异常方式呈报错误

void CMessage::Parse(IStream* input) { try {

m_uiMessageLen = input.ReadInteger();//失败会抛出异常

if (0 == m_uiMessageLen || m_uiMessageLen>MAX_MESSAGE_LEN) {

throw invalid_argument(\}

m_pMessage = new char[m_uiMessageLen];//失败抛出异常 input.Read(m_pMessage, m_uiMessageLen);//失败抛出异常 //..... }

catch (const exception &exp){

ResetContent();//把对象的所有字段都设置为无效