GoogleCpp风格指南 5) 其他特性_part2

5.10 前置自增和自减 Preincrement and Predecrement

Tip 对于迭代器iterator和其他模板对象template object使用前缀形式(++i)的自增, 自减运算符;

定义:

对于变量在自增(++i 或 i++)或自减(--i 或 i--)后, 表达式的值没有被用到的情况下, 需要确定到底是使用前置还是后置的自增(自减);

优点:

不考虑返回值的话, 前置pre自增(++i)通常要比后置post自增(i++)效率更高; 因为后置自增(自减)需要对表达式的值 i 进行一次拷贝; 如果i是迭代器或其他非数值 non-scalar类型, 拷贝的代价是比较大的; 既然两种自增方式实现的功能一样, 为什么不总是使用前置自增呢?

缺点:

在C开发中, 当表达式的值未被使用时, 传统的做法还是使用后置自增, 特别是在 for循环中; 有些人觉得后置自增更加易懂, 因为这很像自然语言, 主语subject(i)在谓语动词precede(++)前;

[C语言中没有class类型, 基本上POD不必在意前置或后置的效率区别]

结论:

对简单数值scalar(非对象non-object), 两种都无所谓; 对迭代器和模板类型, 使用前置自增(自减);

5.11 const的使用 Use of const

Tip 强烈建议在任何可能的情况下都要使用 const; [Add] c++11中, constexpr对于某些const使用情况是更好的选择; [http://en.cppreference.com/w/cpp/language/constexpr]
<<<

定义:

在声明的变量或参数前preceded加上关键字 const用于指明变量值不可被篡改(如 const int foo); 为类中的函数加上 const限定符qualifier表明该函数不会修改类成员变量的状态(如 class Foo { int Bar(char c) cosnt; }; )

优点:

大家更容易理解如何使用变量; 编译器可以更好地进行类型检测; 相应地, 也能生成更好的代码; 人们对编写正确的代码更加自信, 因为他们知道所调用的函数被限定了能或不能修改变量值; 即使是在无锁的多线程编程中without locks in multi-threaded, 人们也知道什么样的函数是安全的;

缺点:

const是入侵性viral的: 如果你向一个函数传入 const变量, 函数原型声明中也必须对应 const参数(否则变量需要 const_cast类型转换), 在调用库函数是显得尤其麻烦;

结论:

const变量, 数据成员, 函数和参数为编译时类型检测增加了一层保障: 便于尽早发现错误; 因此, 我们强烈建议在任何可能的情况下使用 const;

- 如果函数不会修改传入的引用或指针类型参数, 该参数应声明为const;

- 尽可能将函数声明为const; 访问函数几乎总是const; 其他不会修改任何数据成员, 没有调用非const函数, 不会返回数据成员非const指针或引用的函数也应声明成const;

- 如果数据成员在对象构造之后不再发生变化, 可将其定义为const;

[Remove] 然而, 也不要发疯似的使用const; 像 const int* const* const x; 就有些过了, 虽然它非常精确地描述了常量 x; 关注真正有帮助一样的信息: 前面的例子写成 const int** x就够了; [内容不可变] <<<

关键字 mutable可以使用, 但是在多线程中是不安全的, 使用时首先要考虑线程安全性;

const的位置 Where to put the const:

有人喜欢 int const *foo形式, 不喜欢 const int* foo; 他们认为前者更一致因此可读性也更好: 遵循了const总位于其描述的对象之后的原则; 但是一致性原则不适用于此, 由于多数const表达式只有一个const, 而且应用的是一个值, 很少有深层嵌套的指针表达式few deeply-nested pointer expressions; "不要过度使用"的声明可以取消大部分你原本想保持的一致性;
将const放在前面才更易读, 因为在自然语言中形容词adjective(const)是在名词noun(int)之前;

这是说, 我们提倡但不强制const在前; 但要保持代码的一致性; (译注, 就是不要在一些地方把const写在类型前面, 在其他地方又写在后面, 要确定一种写法, 然后保持一致);

[Add]

使用constexpr Use of constexpr

C++11中, 使用constexpr来定义true的常量或确保常量初始化constant initialization;

定义:

一些变量可以被声明为constexpr, 表明变量是true的常量, e.g. 在编译/链接时是固定的; 一些函数和cotr可以被声明为constexpr, 让它们可以在定义一个constexpr变量时被使用;

优点:

使用constexpr定义浮点数floating-point表达式常量, 而不是字面量literal定义, 用户定义的类型的定义和函数调用function call的常量的定义;

缺点:

把某些东西定义为constexpr可能会在之后导致一些迁移migration问题, 或许不得不回退回去downgraded; 目前的对于constexpr函数和ctor的限制规定restriction可能会在这些定义中产生些隐晦的替代方案workaround;

结论:

constexpr定义可以在一些接口的const部分给出健壮的规格robust specification; 使用constexpr来指定真实常量true constants以及支持函数的定义; 使用constexpr来防止复杂的函数定义; 不要使用constexpr来强制内联 inline; [http://stackoverflow.com/questions/14391272/does-constexpr-imply-inline ]

<<<

5.12 整型 Integer Types

Tip C++内建整型中, 仅使用 int; 如果程序中需要不同大小的变量, 可以使用 <stdint.h>中长度精确precise-width的整型, 如 int16_t; [http://www.cplusplus.com/reference/cstdint/ ]

[Add] 如果你的变量表示了一个可以变大或者等于2^31(2GiB)的值, 使用一个64-bit类型, 比如int64_t; 记住即使你的值对于int来说不会过大, 它还是可能在一些中间计算中需要一个更大的类型; 如果不确定, 就选用更大的类型; <<<

定义:

C++没有指定整型的大小; 通常人们假定 short是16位, int是32位, long是32位, long long是64位; (bits)

优点:

保持声明统一性Uniformity;

缺点:

C++中整型大小因编译器和体系结构architecture的不同而不同;

结论:

<stdint.h>定义了 int16_t, uint32_t, int64_t等整型, 在需要确保guarantee 整型大小时可以优先preference使用它们代替 short, unsigned long long等; 在C整型中, 只使用 int; 在合适的情况下, 推荐使用标准类型如 size_tptrdiff_t;

如果已知整数不会太大, 我们常常会使用 int, 如循环计数loop counter; 在类似的情况下使用原生类型plain old int; 你可以认为int至少为32位, 但不要认为它会多于32位; 如果需要64位整型, 用 int64_t或 uint64_t;

对于大整数, 使用 int64_t;

不要使用 uint32_t等无符号整型, 除非有正当valid 理由, 比如在表示一个位组bit pattern而不是一个数值, 或是需要定义二进制补码溢出overflow modulo 2^N; 尤其是不要为了指出数值永远不会为负, 而使用无符号类型; 相反, 你应该用断言来保护数据;

<<<

[Add] 如果你的代码是一个返回大小的容器, 确保使用一个可适应accommodate任何使用的可能性的数据类型; 不确定的时候使用一个更大的类型而不是较小的类型;

当转化整型的时候要小心; 整型转换和提升promotion会引起一些反直觉non-intuitive的行为;

<<<

关于无符号整数 On Unsigned Integers:

有些人, 包括一些教科书作者, 推荐使用无符号整型表示非负数; 这种做法试图达到自我文档化self-documentation; 但是在C语言中, 这一优点被由其导致的bug所淹没outweighed; 看看下面的例子:


1

for (unsigned int i
= foo.Length()-1; i >= 0; --i) 
//...

上述循环永远不会退出! 有时gcc会发现该bug并报警, 但大部分情况下都不会; 类似的bug还会出现在比较有符号变量和无符号变量时; 主要是C的类型提升机制会导致无符号类型的行为出乎你的意料;

因此, 使用断言来指出变量为非负数, 而不要使用无符号类型;

5.13 64位下的可移植性 64-bit Portability

Tip 代码应该对64位和32位系统友好; 处理打印printing, 比较comparison, 结构体对齐structure alignment时应该切记;

- 对于某些类型, printf()的指示符在32位和64位系统上可移植性不是很好; C99标准定义了一些可移植的格式化指示符; 不幸的是, MSVC7.1并非全部支持; 而且标准中也有所遗漏, 所以有时我们不得不自己定义一个丑陋的版本(头文件inttype.h仿标准风格):


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// printf macros for size_t, in the style of inttypes.h

#ifdef _LP64

#define __PRIS_PREFIX "z"

#else

#define __PRIS_PREFIX

#endif

// Use these macros after a % in a printf format string

// to get correct 32/64 bit behavior, like this:

// size_t size = records.size();

// printf("%"PRIuS"\n", size);

#define PRIdS __PRIS_PREFIX "d"

#define PRIxS __PRIS_PREFIX "x"

#define PRIuS __PRIS_PREFIX "u"

#define PRIXS __PRIS_PREFIX "X"

#define PRIoS __PRIS_PREFIX "o"

check table:

Type DO NOT use DO use Notes
void * (or any pointer) %lx             %p              
int64_t             %qd%lld             %"PRId64"              
uint64_t             %qu%llu%llx             %"PRIu64"%"PRIx64"              
size_t             %u             %"PRIuS"%"PRIxS"             C99 specifies %zu            
ptrdiff_t             %d             %"PRIdS"             C99 specifies %td            

Note PRI*宏会被编译器扩展concatenated为独立字符串; 因此如果使用非常量的格式化字符串, 需要将宏的值而不是宏名插入格式中; 使用PRI*宏同样可以在 %后包含长度指示符,etc; 例如: printf("x = %30"PRIuS"\n", x); 在32位Linux将被展开为: printf("x = %30" "u" "\n", x); 编译器当成: printf("x = %30u\n",
x);处理; (译注: 这在MSVC6.0上不行, VC6编译器不会自动把引号间隔的多个字符串连接成一个长字符串);

- 记住 sizeof(void *) != sizeof(int); 如果需要一个指针大小的整数要用 intptr_t; [不同编译器, 系统不一样]

- 要非常小心地对待结构体对齐, 尤其是要持久化存储到磁盘上的结构体; (译注: 持久化--将数据按字节流顺序保存在磁盘文件或数据库中); 在64位系统中, 任何含有 int64_t/uint64_t成员的类/结构体, 缺省都以8字节在结尾对齐; 如果32位和64位代码要共用持久化在磁盘上的结构体; 需要确保两种体系结构下的结构体对齐一致;(packed) 大多数编译器都允许调整结构体对齐;
gcc中可使用 __attribute__((packed)); MSVC则提供了 #pragma pack()和 __deslspec(align());

(译注: 解决方案的项目属性栏里也可以直接设置) [VS的项目--solution]

[http://en.wikipedia.org/wiki/Data_structure_alignment ]

- 创建64位常量时使用 LL或 ULL作为后缀suffixes, 如:


1

2

int64_t my_value = 0x123456789LL;

uint64_t my_mask = 3ULL << 48;

- 如果你确实需要32位和64位系统具有不同代码, 可以使用 #ifdef _LP64指令在代码变量中区分 32/64位代码; (尽量不要这么做, 如果非用不可, 尽量使修改局部化);

[http://stackoverflow.com/questions/685124/how-to-identify-a-64-bit-build-on-linux-using-the-preprocessor


1

2

3

#if defined(__LP64__) || defined(_LP64)

#define BUILD_64   1

#endif

]

5.14 预处理宏 Preprocessor Macros

Tip 使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量替代之;

宏意味着你和编译器看到的代码是不同的; 这可能会导致异常行为, 尤其因为宏具有全局作用域;

值得庆幸的是, C++中, 宏不像在C中那么必不可少; 以往用宏展开性能关键performance-critical的代码, 现在可以用内联函数替代; 用宏表示的常量可以被 const变量代替; 用宏"缩写"长变量的别名abbreviate可被引用代替reference; [以及typedef]; 用宏进行条件编译...这个, 千万别这么做, (#define防止头文件重复包含当然是个特例) 会令测试更加痛苦;

宏可以做一些其他技术无法实现的事情, 在一些代码库(尤其是底层库中)可以看到宏的某些特性(如用#字符串化stringifying, 用##连接concatenation等); 但在使用前, 仔细考虑一下能不能不使用宏达到同样的目的; [Hack: private和public]

下面给出的用法模式可以避免使用宏带来的问题; 如果要用宏, 尽可能遵守:

- 不要在 .h文件中定义宏;

- 在马上要使用时才进行 #define, 使用完要立即 #undefine;

- 不要只是对已经存在的宏使用 #undef, 选择一个不会冲突的独特名称;

- 不要试图使用展开后会导致C++构造不稳定的宏, 否则至少要附上文档说明其行为; [不要在构造相关代码使用宏?]

- 最好不要使用 ##来产生 function/class/variable的名字;

5.15 0 and nullptr/NULL

Tip 整数用 0, 实数用 0.0, 指针用 NULL或nullptr, 字符chars(串)用 ‘\0‘;

整数用0, 实数用0.0, 这一点毫无争议controversial;

对于指针(地址值), 到底是使用0还是NULL/nullptr,

[Remove]Bjarne Stroustrup建议使用最原始的0; 我们建议使用看上去像是指针的 NULL;  [C++11: nullptr]<<<

[Add] 对允许C++11的项目, 使用nullptr, C++03项目使用NULL, 看起来比较像个指针; <<<

事实上一些C++编译器(如gcc4.1.0)对 NULL进行了特殊的定义, 可以给出有用的警告信息, 尤其是 sizeof(NULL)和 sizeof(0)不相等的情况;

字符(串)用 ‘\0‘, 不仅类型正确而且可读性好; [http://bbs.csdn.net/topics/390615761]

5.16 sizeof

Tip 尽可能用 sizeof(varname)代替 sizeof(type);

使用 sizeof(varname)是因为当代码中变量类型改变时会自动更新; 某些特定情况下 sizeof(type)或许有意义, 比如管理external或internal数据类型变量的代码, 而没有方便的C++类型; 但还是要尽量避免, 因为它会导致变量类型改变后不能同步;


1

2

Struct data;

memset(&data,
0, 
sizeof(data));

WARNING 


1

memset(&data,
0, 
sizeof(Struct));

[Add]

Other


1

2

3

4

if (raw_size
sizeof(int))
{

  LOG(ERROR)
<< 
"compressed record not big enough for count: " <<
raw_size;

  return false;

}

<<<

[Add]

auto

使用auto来避免类型名字杂乱clutter; 当有助于可读性的时候继续使用明显的manifest类型声明, 除了本地local变量之外不要使用auto;

定义:

C++11中, 一个由auto指定类型的变量会给出符合初始化它的表达式的类型; 可以使用auto来用copy初始化initialize它, 或者绑定bind一个引用;

[http://en.cppreference.com/w/cpp/language/auto]


1

2

3

4

vector<string> v;

...

auto s1
= v[0];  
// Makes a copy of v[0].

const auto&
s2 = v[0];  
// s2 is a reference to v[0].

优点:

C++类型名称有时候很长而且笨重cumbersome, 特别是在模板或名字空间中:


1

sparse_hash_map<string, int>::iterator
iter = m.find(val);

返回值很难读懂, 蒙蔽obscure了语句的原本意图; 改为:


1

auto iter
= m.find(val);

更易读懂;

没有auto的话我们有时不得不将一个类型名字在一个表达式内写两遍, 对于阅读者来说没有意义:


1

diagnostics::ErrorStatus* status = new diagnostics::ErrorStatus("xyz");

使用auto让中间intermediate变量的使用更合理, 减少了显式书写类型的负担;

缺点:

有时候变量是manifest的会更清晰, 特别是变量初始化依赖于我们之前声明的东西; 像这样的表达式:


1

auto i
= x.Lookup(key);

如果x是几百行之前声明的, i的类型可能不够明显;

程序员不得不去理解auto和const auto&之间的区别, 有时候他们会在没有意识到的时候拿到一份copy;

auto和C++11 brace-initialization之间的交互interaction可能会令人混淆; 声明:


1

2

auto x(3);  //
Note: parentheses.

auto y{3};  //
Note: curly braces.

它们表示不同的东西, x是个int, 但y是一个std::initializer_list<int>; 相同情况会发生在其他普通隐式代理normally-invisible proxy类型上;

如果一个auto变量被用作接口的一部分, e.g. 在头文件中作为一个const, 程序员可能只是为了改变它的值而改变它的类型, 导致没有想到的一系列API的彻底radical改变;

结论:

auto只对本地变量开放; 不要在文件范围file-scopye或名字空间范围namespace-scope中对变量, 或类成员使用auto; 永远不要用大括号初始化列表braced initializer list初始化一个auto类型auto-typed变量;

auto关键字也用在C++feature无关的地方: 它作为一种新的函数声明的语法的一部分, 尾随返回值类型 trailing return type; trailing return type只在lambda表达式中被允许使用;

Braced Initializer List

braced initializer lists; [http://en.cppreference.com/w/cpp/language/list_initialization]

在C++03, 聚合类型aggregate type(没有ctor的数组和结构体)可以用braced initializer list初始化;


1

2

struct Point
int x; int y;
};

Point p = {1, 2};

C++11中, 这个语法被普遍化generalized了, 任何一个对象类型都可以用braced initializer list来初始化, 作为一个braced-init-list的C++语法grammar; 这里有几个例子:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

// Vector takes a braced-init-list of elements.

vector<string> v{"foo""bar"};

// Basically the same, ignoring some small technicalities.

// You may choose to use either form.

vector<string> v = {"foo""bar"};

// Usable with ‘new‘ expressions.

auto p
new vector<string>{"foo""bar"};

// A map can take a list of pairs. Nested braced-init-lists work.

map<int,
string> m = {{1, 
"one"},
{2, 
"2"}};

// A braced-init-list can be implicitly converted to a return type.

vector<int>
test_function() { 
return {1,
2, 3}; }

// Iterate over a braced-init-list.

for (int i
: {-1, -2, -3}) {}

// Call a function using a braced-init-list.

void TestFunction2(vector<int>
v) {}

TestFunction2({1, 2, 3});

一个用户定义的类型也可以使用std::initializer_list<T> [http://en.cppreference.com/w/cpp/utility/initializer_list] 定义一个ctor或assignment operator, 会从braced-init-list自动创建;


1

2

3

4

5

6

7

8

9

10

11

12

13

class MyType
{

 public:

  //
std::initializer_list references the underlying init list.

  //
It should be passed by value.

  MyType(std::initializer_list<int>
init_list) {

    for (int i
: init_list) append(i);

  }

  MyType&
operator=(std::initializer_list<
int>
init_list) {

    clear();

    for (int i
: init_list) append(i);

  }

};

MyType m{2, 3, 5, 7};

最后 brace initialization也能调用普通的数据类型的ctor, 即使没有std::initializer_list<T>构造函数;


1

2

3

4

5

6

7

8

9

10

11

double d{1.23};

// Calls ordinary constructor as long as MyOtherType has no

// std::initializer_list constructor.

class MyOtherType
{

 public:

  explicit MyOtherType(string);

  MyOtherType(int,
string);

};

MyOtherType m = {1, "b"};

// If the constructor is explicit, you can‘t use the "= {}" form.

MyOtherType m{"b"};

Note 永远不要把一个braced-init-list分配给一个auto的本地变量; 在单个元素的case中, 其意义可能会混淆:

Bad:


1

auto d
= {1.23};        
// d is a std::initializer_list<double>

Good:


1

auto d
double{1.23};  //
Good -- d is a double, not a std::initializer_list.

参见 Braced Initializer List Format;

<<<

---TBC---YCR

时间: 2024-10-06 10:40:00

GoogleCpp风格指南 5) 其他特性_part2的相关文章

GoogleCpp风格指南 5) 其他特性_part1

5 其他C++特性 Other C++ Features 5.1 引用参数 Reference Arguments Tip 所有按引用传递的参数必须加上 const; 定义: 在C语言中, 如果函数需要修改变量的值, 参数必须为指针, 如 int foo(int* pval); 在C++中, 函数还可以声明引用参数 int foo(int& val); 优点: 定义引用参数防止出现 (*pval)++ 这样丑陋的代码; 像拷贝构造函数这样的应用也是必需的, 而且更明确, Note 不接受NULL

GoogleCpp风格指南 5) 其他特性_part3

[Add] Lambda expressions 在合适的时候使用lambda表达式; 不要使用默认的lambda captures, 使用显式的captures; [http://en.cppreference.com/w/cpp/language/lambda ] 定义: lambda表达式是一个创建匿名函数对象anonymous function objects.的简洁concise方式; 在将函数作为参数传递的时候很有用; 1 2 3 std::sort(v.begin(), v.end

GoogleCpp风格指南 1)头文件 2)作用域

Google开源项目风格指南 v3.133 原作: Benjy Weinberger, Craig Silverstein, Gergory Eitzmann, Mark Mentovai, Tashana Landray 翻译: YuleFox, brantyoung 修改: YCR 0 扉页 0.1 译者前言 Google经常发布一些开源项目, 因此发布这份编程风格, 使所有提交代码的人能获知Google的编程风格; 规则的作用是避免混乱, 但规则本身要权威, 有说服力, 并且是理性的, 大

GoogleCpp风格指南 8)格式 _part2

8.9 布尔表达式 Boolean Expressions Tip 如果一个布尔表达式超过标准行宽standard line length; 断行方式要统一一下; 下例中, 逻辑与(&&)操作符总位于行尾: 1 2 3 4 5 if (this_one_thing > this_other_thing &&     a_third_thing == a_fourth_thing &&     yet_another & last_one) {

GoogleCpp风格指南 4)Google奇技

4 来自Google的奇技 Google-Specific Magic Google用了很多自己的实现技巧/工具使 C++代码更加健壮, 我们使用C++的方式可能和你在其他地方见到的有所不同; [Removed] 4.1 智能指针 Tip 如果确实需要使用智能指针的话, scoped_ptr完全可以胜任; 你应该只在非常特定的情况下使用 std::tr1::shared_ptr, 例如STL容器中的对象; 任何情况下都不要使用 auto_ptr; "智能"指针看上去是指针, 其实是附加

GoogleCpp风格指南 3)类

3 类 Classes 类是C++中代码的基本单元; 显然, 它们被广泛使用; 本节列举了写一个类时的主要注意事项; 3.1 构造函数的职责 Doing Work in Constructors Tip 构造函数中只进行那些没什么意义的(trivial 译注: 简单初始化对于程序执行没有实际的逻辑意义, 因为成员变量"有意义"的值大多不再构造函数中确定)初始化, 可能的话, 使用 Init()方法集中初始化有意义的(non-trival)数据; [Add] 在ctor中防止做复杂的初始

GoogleCpp风格指南 8)格式 _part1

8 格式 Formatting 代码风格和格式确实比较随意, 但一个项目中所有人遵循同一风格是非常容易的; 个体未必同意下述每一处格式规则, 但整个项目服从统一的编程风格是很重要的, 只有这样才能让所有人很轻松地阅读和理解代码; 我们写了一个 settings file for emacs [http://google-styleguide.googlecode.com/svn/trunk/google-c-style.el] 帮助你正确的格式化代码; 8.1 行长度 Line Length T

GoogleCpp风格指南 7)注释

7 注释 Comments 注释虽然写起来很痛苦, 但对保证代码可读性至关重要; 下面的规则描述了如何注释以及在哪注释; 当然也要记住: 注释固然很重要, 但最好的代码本身应该是文档化self-documenting; 有意义的类型名和变量名, 要远胜过要用注释解释的含糊不清的名字; 你写的注释是给代码读者看的: 下一个需要理解你代码的人, 慷慨些吧, 下一个人可能就是你; 7.1 注释风格 Comment Style Tip 使用 // 或 /* */, 统一就好; // 或 /* */都可以

GoogleCpp风格指南 6)命名约定

6 命名约定 Naming 最重要的一致性规则是命名管理govern naming; 命名风格快速获取名字代表是什么: 类型? 变量, 函数, 常量, 宏, ...甚至不需要去查找类型声明; 大脑中的模式匹配引擎pattern-matching engine可以非常可靠的处理这些命名规则; 命名规则具有一定随意性, 但相比按个人喜好命名, 一致性更重要, 所以不管你怎么想, 规则总归是规则; 6.1 通用命名规则 General Naming Rules Tip 函数命名, 变量命名, 文件命名