高质量C++编程补充条款

1. 前言

介绍高质量C++编程的书籍很多,而且都非常好,这里主要针对已有书籍较少涉及到的代码格式条款进行补充。代码是程序员脸面,清清爽爽和干干净净的代码是程序员高职业素质的体现,清爽的代码需要从细节做起,用心呵护。

2. 条款:避免使用非众所周知的缩略语

本条款非新鲜的,但实际很少有人真正遵循,在代码中总能见到一些自创的缩略语,后来接手代码的人常常需要去猜测是啥意思。因此提出来重点强调一下,如:GeneralServer,不要写成CGSConfig,而应当使用长名CGeneralServerConfig,长名是自注释的,前者在上下文环境不足和缺少文档的情况(除了华为那样文档要求非常严格的企业,可能一般公司都存在这样的情况)下可能需要去猜测。

3. 条款:规范好#include

写#include也有讲究,通常<>在前,""在后,如:

#include 

#include "mooon.h"

而且非隶属本编译工程中的头文件,一律使用<>,隶属本编译工程中的头文件使用""。

4. 条款:避免长短语句无规律交错

下面这段代码无规律的交错着,容易给人以混乱的感觉:

void reset_current_message(bool finish);

void free_current_message();

void inc_resend_times();    

util::handle_result_t do_handle_reply(); 

void clear_message();               

net::epoll_event_t do_send_message(void* ptr, uint32_t events);

使用这一条款后,变成成如下:

    void clear_message();    

    void inc_resend_times();      

    void free_current_message();

    void reset_current_message(bool finish);

    util::handle_result_t do_handle_reply();            

    net::epoll_event_t do_send_message(void* ptr, uint32_t events);

从短到长,明显清爽清晰了很多,变量了定义等也应当尽量遵守此条款。

#include段也应当尽量遵循这个规律,如:

#include 

#include 

#include 

#include 

#include 

#include 

#include 

#include "sys/fs_util.h"

#include "sys/close_helper.h"

如果一些变量是相关的,则可以使用空行分开,在同一组内实施这一条款。

5. 条款:避免头重脚轻

char* str = get_value("thread_number");

if (str != NULL)

{

thread_number = string2int(str);

if (0 == thread_number)

_thread_number = 1;

else

_thread_number = thread_number;

}

else

{

_thread_number = 1;

}

上面的代码段,就显得头重脚轻,if块比else块大了很多。特别是当if块超过50行时,会导致else块较难看,甚至可能难以一下确定else对应哪个if语句。将两者跌倒一下,就可以消除头重脚轻的问题,如下:

char* str = get_value("thread_number");

if (NULL == str)

{

_thread_number = 1;

}

else

{

thread_number = string2int(str);

if (0 == thread_number)

_thread_number = 1;

else

_thread_number = thread_number;

}

6. 条款:充分利用public和private等

C++允许public等修饰符在一个类的定义中多次重复出现,充分利用这一特性,可使得类的定义代码变得更清爽。下面这段代码充分利用了这一特性,对类的定义进行了归类,使得整个定义显得较为清爽不凌乱交错:

class CSender: public net::CTcpClient

{   

public: // 公有函数

    ~CSender();

    CSender(CSendThreadPool* thread_pool, int32_t route_id, uint32_t queue_max, IReplyHandler* reply_handler);

    int32_t get_node_id() const;       

    bool push_message(dispatch_message_t* message, uint32_t milliseconds);    

public: // 公有的虚拟函数

virtual void after_connect();

private: // 重写的虚拟函数

    virtual void before_close();

    virtual void connect_failure();

private: // 非重写的私有函数

    void clear_message();    

    void inc_resend_times();      

    void free_current_message();

    void reset_current_message(bool finish);

    util::handle_result_t do_handle_reply();            

    net::epoll_event_t do_send_message(void* ptr, uint32_t events);

protected: // 提供给不同子类使用的公共函数

    void do_set_resend_times(int8_t resend_times);

    net::epoll_event_t do_handle_epoll_event(void* ptr, uint32_t events);

private: // 非状态成员

    int32_t _route_id;    

    CSendQueue _send_queue;        

    IReplyHandler* _reply_handler;

    CSendThreadPool* _thread_pool;

private: // 发送状态相关的

    int8_t _cur_resend_times;  // 当前已经连续重发的次数

    int8_t _max_resend_times; // 失败后最多重发的次数,负数表示永远重发,0表示不重发

    uint32_t _current_offset;             // 当前已经发送的字节数

    dispatch_message_t* _current_message; // 当前正在发送的消息

};

7. 条款:类成员优先使用对象类型

按照UML上的术语来说,就是优先使用组合,而非聚合,虽然从依赖性上讲聚合低于组合,但这只是理论上,对于一个对象的生命周期由别一个类来掌握时,使用组合更好,原因是组合使得该类对象的内存空间连续,而聚合通常需要在构造函数中new,在析构中delete,容易造成更多的内存碎片,总是连续的比非连续的好,如:

class CAgentThread

{

private:

CMasterConnector _connector; // 建议使用对象类型

};

当然如果只是关联关系,那肯定只能使用指针类型了,如:

class CAgentThread

{

private:

CAgentContext* _contexnt; // 只能使用指针类型

}

8. 条款:名字空间的使用

杜绝在头文件使用using,包括using namespace std和using std::vector两种形式。这样做完全失去了名字空间的意义,减少名字间的冲突。

9. 条款:巧用do...while(false)替代goto

先看下段代码:

int CTcpClient::timed_connect()

{

int fd = socket(AF_INET, SOCK_STREAM, 0);

if (-1 == fd)

{

return errno; // goto CONNECT_ERROR:

}

if (-1 == connect(fd, peer_addr, addr_length))

{

close(fd);

return errno;

}

if (!CNetUtil::timed_poll(fd, POLLIN | POLLOUT | POLLERR, _milli_seconds))

{

close(fd);

return errno;

}

if (-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &errcode_length))

{

close(fd);

return errno;

}

set_fd(fd);

return 0;

}

上面这段代码,在出错的地方,有多处return,代码基本相同,通常大家会想到使用goto语句来解决这个问题。goto总是应当只作为最后不得已的一种选择,通过下面这段代码我们来看看如何使用do...while(false)优雅的解决这个问题:

int CTcpClient::timed_connect()

{

int fd = -1;

do

{

fd = socket(AF_INET, SOCK_STREAM, 0);

if (-1 == fd)

{

break; // goto CONNECT_ERROR:

}

if (-1 == connect(fd, peer_addr, addr_length))

{

close(fd);

break;

}

if (!CNetUtil::timed_poll(fd, POLLIN | POLLOUT | POLLERR, _milli_seconds))

{

close(fd);

break;

}

if (-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &errcode_length))

{

close(fd);

break;

}

set_fd(fd);

return 0;

}

while (false);

// 相当于goto到这里

if (fd != -1) close(fd);

return errno;

}

使用do...while(false)后,整个函数就只有两个return出口了。

10. 条款:利用typedef增强代码的自注释

在一些开源和C++标准库stl中,可以见到大量的typedef使用,除了使用typedef来简化长类型的定义,如:typedef basic_string string;外,还有增强代码自注释的目的。

假设需要一个存储IP端口号的列表,可以定义如下:

std::list port_list_t;

11. 条款:不要失去对进程和线程的控制权

在设计和代码中,应当杜绝时长未定或较长的sleep调用,以及完全阻塞的accept/read等调用,因为这会使你失去对进程和线程的控制权。当你需要进行死锁检测,将不容易区分,当程序需要退出,会比较麻烦。正确的做法是保证sleep的时间尽可能短而且最长时间明确,通常不要超过10秒,甚至可以考虑使用可唤醒的条件等替代,而accept/read应当改用带超时的,或使用非阻塞的,这样就能牢牢把握对进程和线程的控制权。

待续 。。。。。。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏xingoo, 一个梦想做发明家的程序员

Elasticsearch聚合初探——metric篇

Elasticsearch是一款提供检索以及相关度排序的开源框架,同时,也支持对存储的文档进行复杂的统计——聚合。 前言 ES中的聚合被分为两大类:Met...

23110
来自专栏数说工作室

【SAS Says】基础篇:7. SAS宏初步

宏用来处理重复工作最好,比如你需要跑10个回归,用proc reg...,这10个回归其他都一样,就是因变量y每次需要换。那么将回归程序写成一个宏,每次用的时候...

3856
来自专栏晨星先生的自留地

关于一次渗透引发的一个php木马的分析

3735
来自专栏玩转JavaEE

MongoDB管道操作符(二)

上篇文章中我们已经学习了MongoDB中几个基本的管道操作符,本文我们再来看看其他的管道操作符。 ---- $group 基本操作 $group可以用来对文档进...

2926
来自专栏牛客网

考点总结:互联网校招技术岗都考些什么?数据结构算法游戏 + 场景c++面向对象javaJVMSpringandroid数据库计网线程安全linux前端询问面试官

数据结构 红黑树 pk 平衡二叉树 hash表处理冲突的方法 算法 手写 最长无重复字符子串 链表的增、删、查、逆序 数组实现队列,要求可以动态扩展,保证较高的...

3447
来自专栏张善友的专栏

数据压缩算法LZO (C#)

LZO 是致力于解压速度的一种数据压缩算法,LZO 是 Lempel-Ziv-Oberhumer 的缩写。这个算法是无损算法,参考实现程序是线程安全的。 实现它...

3319
来自专栏Java开发者杂谈

分布式改造剧集1

背景介绍 ​ 我所在的项目组,使用的技术一直是接近原始社会的:jdk1.6 + SpringMVC + hessian + Mybatis,当前最火的中间件技术...

2934
来自专栏何俊林

Android Multimedia框架总结(九)Stagefright框架之数据处理及到OMXCodec过程

不知不觉到第九篇了,感觉还有好多好多没有写,路漫漫其修远兮 ,吾将上下而求索。先说福利吧,此前在关于我, ? 曾说过,不定期搞活动,vip,书啥的,都可以有,...

2326
来自专栏北京马哥教育

shell十三问,为linux学习打基础(一)

本文整理并转自CU上的帖子[学习共享] shell 十三問?,此贴是2003年发表的,但却是相当不错的linux基础知识汇集贴,原帖主使用的台湾风格,本文加以简...

3684
来自专栏牛肉圆粉不加葱

Spark 内存管理的前世今生(下)

在《Spark 内存管理的前世今生(上)》中,我们介绍了 UnifiedMemoryManager 是如何管理内存的。然而,UnifiedMemoryManag...

1022

扫码关注云+社区