cocos2d-x中的C++ 编码规范

  • 声明务必要看
  • 头文件
  • define用法
  • 前向声明
  • 内联函数
  • -inlh文件
  • 函数参数顺序
  • include的命名和顺序
  • 作用域
  • 命名空间
  • 非命名的命名空间
  • 命名空间的使用规则
  • 嵌套类
  • 非成员函数静态成员函数全局函数
  • 局部变量
  • 静态变量和全局变量
  • 在构造函数里面完成工作
  • 初始化
  • 显式构造函数
  • 拷贝构造函数
  • 委派和继承构造函数
  • 结构体 vs 类
  • 继承
  • 多重继承
  • 接口
  • 操作符重载
  • 访问控制
  • 声明顺序
  • 编写短函数
  • 其它C特性
  • 所有权和智能指针
  • 引用参数
  • 右值引用
  • 函数重载
  • 缺省参数
  • 变长数组和alloca
  • 友元
  • 异常
  • 运行时类型识别
  • 转换
  • 前置自增和自减
  • const用法
  • constexpr用法
  • Integer类型
  • Unsigned Integers类型
  • 64位移植性
  • 预处理宏
  • 0和nullptrNULL
  • sizeof
  • auto
  • 大括号初始化
  • Lambda表达式
  • Boost
  • C11
  • 文件名
  • 类型名
  • 变量名
  • 结构体变量
  • 全局变量
  • 常量名
  • 一般函数
  • 访问器和存储器
  • 命名空间的名称
  • 枚举器名称
  • 宏命名
  • 注释
  • Doxygen
  • 法律声明和作者
  • 类注释
  • 函数注释
  • 函数声明
  • 函数定义
  • 变量注释
  • 类成员
  • 全局变量
  • 实现注释
  • 类数据成员
  • 单行注释
  • nullptrNULL truefalse 1 2 3
  • Donts
  • 标点拼写和语法
  • TODO注释
  • 弃用注释
  • 格式化
  • 行长度
  • 非ASCII字符
  • 空格还是制表位
  • 函数声明与定义
  • 函数调用
  • 大括号初始化列表
  • 条件语句
  • 循环和选择语句
  • 指针和引用表达式
  • 布尔表达式
  • 返回值
  • 变量和数组初始化
  • 预处理器指令
  • 类格式
  • 构造函数初始化列表
  • 命名空间格式化
  • 水平空白
  • 一般
  • 循环和条件
  • 操作符
  • 模版和类型转换
  • 垂直空白
  • 例外的规则
  • 现存的不符合标准的代码
  • Windows代码
  • 赠言

声明(务必要看)

此文档主要参考于cocos官网的帮助文档,但由于没有目录功能,所以自己结合《c++ primer》编写此文档。
在此,为了避免误人子弟特此声明,这并不是c++知识点总结的技能型干货文章,只是    编码规范。。。。。(但是我开始天真的以为这是官网为我们准备的c++学习文档,在重整理的途中还加了很多书上和网上的经典文章(所以,这并不太适合刚入门想学c++的新手看。。。。
在此,再分享一个教学视频:轻松理解Effective C++:

http://edu.9miao.com/course/50

头文件

为了确保各个文件中类的定义一致,类通常被定义在头文件中。一般情况下,每个.CPP文件应该有一个相关的.h文件,而且名字一致。有一些常见的例外,如单元测试代码和只包含一个main函数的cpp文件。
但是可能由于头文件和类文件同时包含其他类的头文件从而导致头文件多次被包含而不安全(会编译出错,库文件不会,有预编译命令)
确保多次包含而安全的常用技术是预处理器,当预处理器看到#include标记时,会自动用该头文件内容替换#include
还有一种功能是头文件保护符,接下来会讲到

define用法

所有头文件应该由#define防护,以避免多重包含。符号名称的格式应该是___H_。

为了保证唯一性,它们应根据在项目的源代码树的完整路径。例如,在文件中FOO项目cocos2dx/sprites_nodes/CCSprite.h应具有以下防护:

这里写代码片
#ifndef COCOS2DX_SPRITE_NODES_CCSPRITE_H_
#define COCOS2DX_SPRITE_NODES_CCSPRITE_H_...#endif  // COCOS2DX_SPRITE_NODES_CCSPRITE_H_
这里写代码片
// Pragma once is still open for debate (讨论)
#pragma once

我们在考虑是是否使用#pragma once,我们不确定他能支持所有平台。
ifndef: 当且仅当变量未定义时为真,一旦检查结果为真,则一直执行后续操作直到遇到#endif指令为止

前向声明

前向声明普通类可以避免不必要的#includes。

定义: “前向声明”是类、函数或者模版的声明,没有定义。用前向声明来替代#include通常应用在客户端代码中。

优点

不必要的#includes会强制编译器打开更多的文件并处理更多的输入。
不必要的#includes也会导致代码被更经常重新编译,因为头文件修改。

缺点

不容易确定模版、typedefs、默认参数等的前向声明以及使用声明。
不容易判断对给定的代码该用前向声明还是#include,尤其是当有隐式转换时。极端情况下,用#include代替前向声明会悄悄的改变代码的含义。
在头文件中多个前向声明比#include啰嗦。
前向声明函数或者模版会阻止头文件对APIs做“否则兼容”(otherwise-compatible)修改;例如,扩展参数类型或者添加带有默认值的模版参数。
前向声明std命名空间的符号通常会产生不确定的行为。
为了前向声明而结构化代码(例如,适用指针成员,而不是对象成员)会使代码更慢更复杂。
前向声明的实际效率提升未经证实。
结论

使用头文件中声明的函数,总是#include该头文件。

使用类模版,优先使用#include。

使用普通类,可以用前向声明,但是注意前向声明可能不够或者不正确的情况;如果有疑问,就用#include。

不应只是为了避免#include而用指针成员代替数据成员。

总是#include实际声明/定义的文件;不依赖非直接包含的头文件中间接引入的符号。例外是,Myfile.cpp可以依赖Myfile.h中的#include和前向声明。

讲了那么多最后还是说多用#include。。。看这个链接吧
http://blog.csdn.net/u012723995/article/details/47137275

内联函数

定义:函数体很小——10行代码以内,用以将函数在调用点内联地展开,从而消除函数的运行时开销。在函数返回类型前面加inline

优点:内联短小精悍的函数可以生成更高效的对象码。推荐内联取值函数、设值函数以及其余性能关键的短函数。

缺点: 滥用内联可能导致程序更慢内联可能让代码尺寸增加或者减少,这取决于函数的尺寸。内联一个非常小的取值函数通常会减少代码尺寸,而内联一个非常大的函数会显著增加代码尺寸。在现代处理器架构下,更小尺寸的代码因为可以更好的利用指令缓存,通常跑得更快。

结论一个黄金法则是不要内联超过10行的函数。要小心析构函数,因为隐含成员和基类的析构函数,它们通常比看上去的要长。

另一个黄金法则:通常不建议内联带循环或者switch语句的函数(除非,大部分情况下,循环或者switch语句不会被执行)

需要注意的是,即便函数被声明为内联他们也不一定会真的内联;例如虚函数以及递归函数一般都不会被内联。通常递归函数不应该被内联。将虚函数内联的主要原因是为了方便或者文档需要,将其定义放在类中,例如取值函数以及设值函数。

-inl.h文件

如果有需要,可以用带-inl.h后缀的文件来定义复杂内联函数

内联函数的定义必须放在头文件中,这样编译器在函数调用处内联展开时才有函数定义可用。但实现代码通常还是放在.cpp文件比较合适,因为除非会带来可读性或者性能上的好处,否则我们不希望在.h文件里堆放太多具体的代码。

如果一个内联函数的定义非常短,只含有少量逻辑,你可以把代码放在你的.h文件里。例如取值函数与设值函数都毫无疑问的应该放在类定义中。更复杂的内联函数为了实现者和调用者的方便,也要放在.h文件里,但是如果这样会让.h文件过于臃肿,你也可以将其放在一个单独的-inl.h文件里。这样可以将具体实现与类定义分开,同时又确保了实现在需要用到的时候是被包含的。

-inl.h文件还有一个用途是存放函数模板的定义。这样可以让你的模板定义更加易读。

不要忘记,就像其他的头文件一样,一个-inl.h文件也是需要#define防护的。

函数参数顺序

定义函数时,参数顺序应该为:输入,然后是输出

C/C++函数的参数要么是对函数的输入,要么是函数给出的输出,要么两者兼是。输入参数通常是值或者常引用,而输出以及输入/输出参数是非const指针。 在给函数参数排序时,将所有仅输入用的参数放在一切输出参数的前面。特别需要注意的是,在加新参数时不要因为它们是新的就直接加到最后去;新的仅输入用参数仍然要放到输出参数前。

这不是一条不可动摇的铁律。那些既用于输入又用于输出的参数(通常是类/结构体)通常会把水搅浑,同时,为了保持相关函数的一致性,有时也会使你违背这条原则。

include的命名和顺序

使用以下标准顺序以增加可读性,同时避免隐藏的依赖关系:C库,C++库,其他库的.h文件,你自己项目的.h文件。

所有本项目的头文件都应该包含从源代码根目录开始的完整路径,而不要使用UNIX的目录快捷方式.(当前目录)或者..(上层目录)。例如google-awesome-project/src/base/logging.h应写为以下方式

#include "base/logging.h"

例如有文件dir/foo.cpp或dir/foo_test.cpp,他们的主要用途是实现或者测试dir2/foo2.h头文件里的内容,那么include的顺序应该如下:

dir2/foo2.h (推荐位置——理由见后)
C system files.
C++ system files.
Other libraries’ .h files.
Your project’s .h files.
按照这个推荐顺序,如果dir2/foo2.h漏掉了什么必须的包含文件,dir/foo.cpp或者dir/foo_test.cpp就会编译失败。这样的规则就保证了工作在这些文件的人而不是在其他包工作的无辜的人最先发现问题。

dir/foo.cpp和dir2/foo2.h通常位于同一个目录(例如base/basictypes_test.cpp和base/basictypes.h),但是在不同目录也没问题。

同一部分中包含文件应该按照字母顺序排列。注意如果老代码不符合这条规则,那就在方便的时候改过来。

例如cocos2dx/sprite_nodes/CCSprite.cpp的include部分可能如下:

#include "sprite_nodes/CCSprite.h"  // Preferred location.#include 
#include 
#include 
#include #include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"

特例:有时候系统相关代码需要使用条件包含。这种情况下可以把条件包含放在最后。当然,要保持系统相关代码短小精悍并做好本地化。例如:

#include "foo/public/fooserver.h"#include "base/port.h" // For LANG_CXX11.
#ifdef LANG_CXX11
#include 
#endif  // LANG_CXX11

作用域

一段程序代码中限定这个名字的可用性的代码范围就是这个名字的作用域。(在哪儿被创建,在哪儿被销毁)

对于对象而言(其他也是一样的),在main函数中,对象的作用域为他所在的最近的一对花括号内。在后花括号处析构函数被调用;全局的对象的作用域为声明之后的整个文件,析构函数在最后被调用。另外,临时产生的对象在使用完后立即会被析构。

允许在内层作用域中重新定义外层作用域已有的名字
C/C++中作用域详解:http://blog.csdn.net/u012723995/article/details/47154289

命名空间

在.cpp文件中,提倡使用未命名的命名空间(unnamed namespaces,注:未命名的命名空间就像未命名的类一样,似乎被介绍的很少:-()。使用命名的命名空间时,其名称可基于项目的路径名称。不要使用using指示符。不要使用内联命名空间。

using namespace 命名空间名称;
using 命名空间名称::成员;

第一种形式中的命名空间名称就是我们要访问的命名空间。该命名空间中的所有成员都会被引入到当前范围中。也就是说,他们都变成当前命名空间的一部分了,使用的时候不再需要使用范围限定符了。第二种形式只是让指定的命名空间中的指定成员在当前范围中变为可见。

定义: 命名空间将全局作用域细分为不同的、命名的作用域,可有效防止全局作用域的命名冲突。 基本形式:namespace 名称

优点: 命名空间提供了(层次化的)命名轴(name axis,注:将命名分割在不同命名空间内),当然,类也提供了(层次化的)的命名轴。(简单讲就是减少命名冲突)

举例来说,两个不同项目的全局作用域都有一个类Foo,这样在编译或运行时造成冲突。如果每个项目将代码置于不同命名空间中,project1::Foo和project2::Foo作为不同符号自然不会冲突。

内联命令空间自动地将名字置于封闭作用域。例子如下:

namespace X {
inline namespace Y {void foo();
}
}

X::Y::foo()和X::foo()是一样的。内联命名空间是为了兼容不同版本的ABI而做的扩展

缺点: 命名空间具有迷惑性,因为它们和类一样提供了额外的(层次化的)命名轴。

特别是内联命名空间,因为命名实际上并不局限于他们声明的命名空间。只有作为较大的版本控制策略的一部分时才有用。

结论: 根据下文将要提到的策略合理使用命名空间。如例子中那样结束命名空间时进行注释。

非命名的命名空间

允许甚至鼓励在.cpp中使用未命名空间,以避免运行时的命名冲突:

定义:关键字namespace后直接跟有花括号括起来的一系列声明语句

未命名的命名空间中定义的变量拥有静态生命周期,在第一次使用前被创建,知道程序结束才销毁。听上去跟静态声明差不多,这里注意:
在文件中进行静态声明的做法已经被c++标准取消了,现在的做法是使用未命名的命名空间

namespace {         // This is in a .cpp file.// The content of a namespace is not indented
enum { UNUSED, EOF, ERROR };         // Commonly used tokens.
bool atEof() { return _pos == EOF; }  // Uses our namespace's EOF.}  // namespace

然而,与特定类关联的文件作用域声明在该类中被声明为类型、静态数据成员与静态成员函数,而不是未命名命名空间的成员不能在.h文件中使用未命名空间。

命名空间的使用规则

命名空间将除文件包含、全局标识的声明/定义以及类的前置声明外的整个源文件封装起来,以同其他命名空间相区分。

// .h文件
// 使用cocos2d命名空间
NS_CC_BEGIN// 所有声明均在命名空间作用域内。
// 注意不用缩进。
class MyClass
{
public:...void foo();
};NS_CC_END
// .h文件
// 不使用cocos2d命名空间
namespace mynamespace {// 所有声明均在命名空间作用域中。
// 注意不用缩进。
class MyClass
{
public:...void foo();
};}  // namespace mynamespace
// .cpp文件
namespace mynamespace {// 函数定义在命名空间作用域中。
void MyClass::foo()
{...
}}  // namespace mynamespace

通常.cpp文件会包含更多、更复杂的细节,包括引用其他命名空间中的类等。

#include "a.h"DEFINE_bool(someflag, false, "dummy flag");class C;  // 前向声明全局作用域中的类C。
namespace a { class A; }  // 前向声明a::A。namespace b {...code for b...         // 代码无缩进。}  // namespace b

不要声明std命名空间里的任何内容,包括标准库类的前置声明。声明std里的实体会导致不明确的行为,例如,不可移植。包含对应的头文件来声明标准库里的实体。 最好不要使用using指示符,以保证命名空间下的所有名称都可以正常使用。

// **禁止--污染了命名空间**。
using namespace foo;

在.h的函数、方法、类,.cpp的任何地方都可以使用using声明。

// 在.cpp中没有问题。
// 在.h中必须在函数、方法或者累中。
using ::foo::bar;

using声明能使名字空间中的一个变量或函数在名字空间外可见,而using指示符则使整个名字空间中的成员在名字空间外都可见,就像去掉名字空间一样.

.h函数方法包含整个.h的命名的命名空间中以及.cpp中,可以使用命名空间别名。

// .cpp中一些常用名的缩写
namespace fbz = ::foo::bar::baz;// .h中一些常用名的缩写
namespace librarian {
// 包括该头文件(在librarian命名空间中)在内的所有文件都可以使用下面的别名:
// 因此同一个项目中的别名应该保持一致。
namespace pd_s = ::pipeline_diagnostics::sidetable;inline void myInlineFunction() {
// 函数或者方法中的本地命名空间别名。
namespace fbz = ::foo::bar::baz;
...
}
}  // namespace librarian

注意,.h文件中的别名对所有包含该文件的所有文件都可见,因此公共的头文件(在项目外仍可用)以及通过他们间接办好的头文件应避免定义别名,为了保持公共的APIs尽可能小。

不要用内联命名空间。

嵌套类

公开嵌套类作为接口的一部分时,虽然可以直接将他们保持在全局作用域中,但将嵌套类的声明置于命名空间中是更好的选择

定义: 可以在一个类中定义另一个类,嵌套类也称成员类(member class)。

class Foo
{
private:// Bar是嵌套在Foo中的成员类class Bar{...};
};

优点: 当嵌套(成员)类只在被嵌套类(enclosing class)中使用时很有用,将其置于被嵌套类作用域作为被嵌套类的成员不会污染其他作用域同名类。可在被嵌套类中前置声明嵌套类,在.cpp文件中定义嵌套类,避免在被嵌套类声明中包含嵌套类的定义,因为嵌套类的定义通常只与实现相关。

缺点: 只能在被嵌套类的定义中才能前置声明嵌套类。因此,任何使用Foo::Bar*指针的头文件必须包含整个Foo类的声明。

结论: 不要将嵌套类定义为public,除非它们是接口的一部分,比如,某方法使用了这个类的一系列选项。

c++的嵌套类使用 http://blog.csdn.net/u012723995/article/details/47155719

非成员函数、静态成员函数、全局函数

优先使用命名空间中的非成员函数或者静态成员函数,尽可能不使用全局函数。

优点: 某些情况下,非成员函数和静态成员函数是非常有用的,将非成员函数置于命名空间中可避免污染全局命名空间。

缺点: 将非成员函数和静态成员函数作为新类的成员或许更有意义,当它们需要访问外部资源戒具有重要依赖时更是如此。

结论: 有时,不把函数限定在类的实体中是有益的,甚至是必要的,要么作为静态成员,要么作为非成员函数。非成员函数不应依赖外部发量,并尽量置于某个命名空间中。相比单纯为了封装若干不共享任何静态数据的静态成员函数而创建类,不如使用命名空间。

定义在同一编译单元的函数,可能会在被其他编译单元直接调用时引入不必要的耦合和链接依赖;静态成员函数对此尤其敏感。可以考虑提取到新类中,戒者将函数置于独立库的命名空间中。

如果你确实需要定义非成员函数,又只是在.cpp中使用,可使用未命名的命名空间或静态关联(如static int Foo() {…})限定其作用域。

局部变量

定义:形参和函数体内部定义的变量

尽可能缩小函数变量的作用域,并在声明变量时将其初始化。

C++允许在函数的任何位置声明发量。我们提倡在尽可能小的作用域中声明变量,离第一次使用越近越好。这使得代码易于阅读,易于定位变量的声明位置、类型和初始值。特别是,应使用初始化代替声明+赋值的方式。

int i;
i = f();      // // 坏——初始化和声明分离int j = g();  // // 好——声明时初始化vector<int> v;
v.push_back(1);  // 优先使用括号初始化。
v.push_back(2);vector<int> v = {1, 2};  // 好-v有初始化。

注意:gcc可正确实现了for (int i = 0; i < 10; ++i)(i的作用域仅限for循环),因此其他for循环中可重用i。if和while等语句中,作用域声明(scope declaration)同样是正确的。

while (const char* p = strchr(str, '/')) str = p + 1;

注意:如果变量是一个对象,每次进入作用域都要调用其构造函数,每次退出作用域都要调用其析构函数。

// 低效实现
for (int i = 0; i < 1000000; ++i) {Foo f;  // My ctor and dtor get called 1000000 times each.f.doSomething(i);
}//类似变量放到循环作用域外面声明要高效的多:Foo f;  // My ctor and dtor get called once each.
for (int i = 0; i < 1000000; ++i) {f.doSomething(i);
}

类似变量放到循环作用域外面声明要高效的多:

静态变量和全局变量

class类型的全局变量是被禁止的:这导致隐藏很深的bugs,因为构造和析构的顺序不


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部