GNU C++ 编程风格

605 阅读5分钟

本文是我在学习libstdc++过程中发现的一篇有关C++编程规范的博客,质量比较高,便翻译整理了一下。原文:gcc.gnu.org/onlinedocs/…

本库(libstdc++)是用C++编写的。因此,我们推荐使用GNU的编程标准,详细的标准参考在这儿:

www.gnu.org/prep/standa…

另外,如果你仍对此感兴趣,可以参考更详细的条例:

gcc.gnu.org/codingconve…

01.指针和引用

char* p = "flop";  
char& c = *p;  
-NOT-  
char *p = "flop";  // wrong  
char &c = *p;      // wrong

说明:在C++中,声明是可以和表达式放在一起的。在这里是p被初始化,而不是*p。这种编写习惯在C++程序员中近乎通用,但对于C程序员需要随着经验的增长而慢慢转变。

02.运算符名称和括号

operator==(type)  
-NOT-  
operator == (type)  // wrong

说明:这里==是函数的名称,如果分开写会让人误以为是表达式。

03.函数名称和括号

void mangle()  
-NOT-  
void mangle ()  // wrong

说明:不要在括号前加空格(除非是控制流关键字)几乎是C++程序员通用规则。这个括号表明是函数调用而不是表达式或者被重载的运算符。

04.模板函数缩进

template<typename T>  
    void  
    template_function(args)  
    { }  
-NOT-  
template<class T>  
void template_function(args) {};

说明:在类声明的上面和下面不应该有缩进空格,这是为了和其他成员作区分。(译注:template关键字顶格写,下面函数声明都要缩进以和其他成员变量、成员函数作区分。)而且,使用typename而不是classT通常可以不是一个类,例如intclass是历史遗留问题。

05.模板类缩进

template<typename _CharT, typename _Traits>  
    class basic_ios : public ios_base  
    {  
    public:  
        // Types:  
    };  
-NOT-  
template<class _CharTclass _Traits>  
class basic_ios : public ios_base  
    {  
    public:  
        // Types:  
    };  
-NOT-  
template<class _CharTclass _Traits>  
    class basic_ios : public ios_base  
{  
public:  
    // Types:  
};

06.枚举

enum  
{  
    space = _ISspace,  
    print = _ISprint,  
    cntrl = _IScntrl  
};  
-NOT-  
enum { space = _ISspace, print = _ISprint, cntrl = _IScntrl };

07.成员初始化列表

都写在一行,类名之间用空格分开

gribble::gribble()  
: _M_private_data(0), _M_more_stuff(0), _M_helper(0)  
{ }  
-NOT-  
gribble::gribble() : _M_private_data(0), _M_more_stuff(0), _M_helper(0)  
{ }

08.try/catch语句

try  
    {  
        //  
    }  
catch (...)  
    {  
        //  
    }  
-NOT-  
try {  
    //  catch(...) {  
    //  
}

09.成员函数声明和定义

类似externstaticexportexplicitinline等关键字后面需要换行。

virtual int  
foo()  
-NOT-  
virtual int foo()

说明:GNU编程条例要求返还类型单独一行,函数名和参数列表在另一行。对于C++这种成员函数要么是内联函数,要么是函数声明的情况下,程序员应该将类中的所有函数名保持对齐以保证可读性。

10.通过"this->"调用成员函数

为了美化用this->name调用函数。

this->sync()  
-NOT-  
sync()

说明:凯尼格查找(Koenig lookup)

11.命名空间

namespace std  
{  
    blah blah blah;  
} // namespace std  
  
-NOT-  
  
namespace std {  
    blah blah blah;  
} // namespace std

12.访问级别的空格

访问级别关键字的上面保留空行,而不是下面

public:  
    int foo;  
  
-NOT-  
public:  
  
    int foo;

13.WRT返回语句的空格

在返回语句之前不需要额外的空行,也不需要加括号

}  
return __ret;  
  
-NOT-  
}  
  
return __ret;  
  
-NOT-  
  
}  
return (__ret);

14.全局变量的位置

所有的class类型的全局变量,无论是“用户可见”空间的(比如cin)还是自己实现的命名空间的,都必须以合理的对齐并初始化。这是因为在AIX等平台会有启动问题(startup issue)导致的,更多解释和例子参考src/globals.cc。该文件包含所有该类型的变量。

15.异常的抽象

functexcept.h内的异常抽象。它可以使程序员通过-fno-exceptions使用本库。(虽然这不是什么建议,但这样做是为了向后兼容。译注:这条不是普适规范)

16.异常错误信息

所有异常必须以抛出异常的函数名称作为开头,然后紧接着一段描述性的文字,文字可以省略。比如:

__throw_logic_error(__N("basic_string::_S_construct NULL not valid"));

说明:verbose terminate handler会指出exception::what(),并且会在异常中附带类型信息。在默认的verbose terminate handler中,通过定位信息可以非常方便的找到问题所在。有经验的程序员甚至可以直接通过观察定位信息直接找到问题而不需要启动debugger。

17.文本生成工具的风格文档应该独立

本库同时包含GNU-C和modern C++的代码风格。GNU-C的代码会逐渐被替换。 (译注:这条不是普适规范)

18.所有的代码每行不应该超过80个字符

19.命名规范

对于那些在标准头文件中出现的非标准命名,我们要求在前面加上下划线,称之为“丑化”。规范如下:

  • 局部变量:__[a-z].*

比如: __count__ix__s1

  • 类型名称和模板中的类型:_[A-Z][^_].*

比如: _Helper_CharT_N

  • 成员变量和成员函数: _M_.*

比如:_M_num_elements  _M_initialize ()

  • 静态变量、常量和枚举: _S_.*

比如:_S_max_elements  _S_default_value

  • 不要使用那些仅仅是前缀不同的变量名称,比如:_S_top和 _M_top。禁止使用这些命名。

  • 为了防止混淆,不要在名称中间加__。同样也不要用__[0-9]作为命名。

20.例子

      #ifndef  _HEADER_  
      #define  _HEADER_ 1  
  
      namespace std  
      {  
        class gribble  
        {  
        public:  
          gribble() throw();  
  
          gribble(const gribble&);  
  
          explicit  
          gribble(int __howmany);  
  
          gribble&  
          operator=(const gribble&);  
  
          virtual  
          ~gribble() throw ();  
  
          // Start with a capital letter, end with a period.  
          inline void  
          public_member(const char* __arg) const;  
  
          // In-class function definitions should be restricted to one-liners.  
          int  
          one_line() return 0 }  
  
          int  
          two_lines(const char* arg)  
          { return strchr(arg, 'a'); }  
  
          inline int  
          three_lines();  // inline, but defined below.  
  
          // Note indentation.  
          template<typename _Formal_argument>  
            void  
            public_template() const throw();  
  
          template<typename _Iterator>  
            void  
            other_template();  
  
        private:  
          class _Helper;  
  
          int _M_private_data;  
          int _M_more_stuff;  
          _Helper* _M_helper;  
          int _M_private_function();  
  
          enum _Enum  
            {  
              _S_one,  
              _S_two  
            };  
  
          static void  
          _S_initialize_library();  
        };  
  
        // More-or-less-standard language features described by lack, not presence.  
      ifndef _G_NO_LONGLONG  
        extern long long _G_global_with_a_good_long_name;  // avoid globals!  
      endif  
  
        // Avoid in-class inline definitions, define separately;  
        // likewise for member class definitions:  
        inline int  
        gribble::public_member() const  
        int __local = 0return __local; }  
  
        class gribble::_Helper  
        {  
          int _M_stuff;  
  
          friend class gribble;  
        };  
      }  
  
      // Names beginning with "__": only for arguments and  
      //   local variables; never use "__" in a type name, or  
      //   within any name; never use "__[0-9]".  
  
      #endif /* _HEADER_ */  
  
  
      namespace std  
      {  
        template<typename T>  // notice: "typename", not "class", no space  
          long_return_value_type<with_many, args>  
          function_name(char* pointer,               // "char *pointer" is wrong.  
                        char* argument,  
                        const Reference& ref)  
          {  
            // int a_local;  /* wrong; see below. */  
            if (test)  
            {  
              nested code  
            }  
  
            int a_local = 0;  // declare variable at first use.  
  
            //  char a, b, *p;   /* wrong */  
            char a = 'a';  
            char b = a + 1;  
            char* c = "abc";  // each variable goes on its own line, always.  
  
            // except maybe here...  
            for (unsigned i = 0, mask = 1; mask; ++i, mask <<= 1) {  
              // ...  
            }  
          }  
  
        gribble::gribble()  
        : _M_private_data(0), _M_more_stuff(0), _M_helper(0)  
        { }  
  
        int  
        gribble::three_lines()  
        {  
          // doesn't fit in one line.  
        }  
      } // namespace std