最近在看谷歌的 C++ 风格指南发现了一些有意思的知识点,遂记录下

1. 第六章第二小节介绍了右值引用

只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用 std::forward.

定义:

右值引用是一种只能绑定到临时对象的引用的一种, 其语法与传统的引用语法相似. 例如, void f(string&& s); 声明了一个其参数是一个字符串的右值引用的函数.

优点:

用于定义移动构造函数 (使用类的右值引用进行构造的函数) 使得移动一个值而非拷贝之成为可能. 例如, 如果 v1 是一个 vector<string>, 则 auto v2(std::move(v1)) 将很可能不再进行大量的数据复制而只是简单地进行指针操作, 在某些情况下这将带来大幅度的性能提升.

右值引用使得编写通用的函数封装来转发其参数到另外一个函数成为可能, 无论其参数是否是临时对象都能正常工作.

右值引用能实现可移动但不可拷贝的类型, 这一特性对那些在拷贝方面没有实际需求, 但有时又需要将它们作为函数参数传递或塞入容器的类型很有用.

要高效率地使用某些标准库类型, 例如 std::unique_ptrstd::move 是必需的.

缺点:

右值引用是一个相对比较新的特性 (由 C++11 引入), 它尚未被广泛理解. 类似引用崩溃, 移动构造函数的自动推导这样的规则都是很复杂的.

结论:

只在定义移动构造函数与移动赋值操作时使用右值引用, 不要使用 std::forward 功能函数. 你可能会使用 std::move 来表示将值从一个对象移动而不是复制到另一个对象.
 
上文说到 std::forward,那么 forward 是干啥的呢?
 
答:std::forward 用于完美转发参数,确保将参数传递给对象的构造函数时保持其原始的左值/右值属性
 
std::forward 经常被拿出与 std::move 比较,这个我在另一篇文章有介绍:
它常常与 std::emplace_back 一起使用,下面是个代码示例,
#include <iostream>
#include <utility>
#include <vector> class MyObject {
public:
MyObject(int value) : value(value) {
std::cout << "Constructor: " << value << std::endl;
} private:
int value;
}; class MyContainer {
public:
// emplace 函数用于构造元素
template <typename... Args>
void emplace(Args&&... args) {
// 使用 std::forward 完美转发参数到构造函数
objects.emplace_back(std::forward<Args>(args)...);
} private:
std::vector<MyObject> objects;
}; int main() {
MyContainer container; int value = 42; // 使用 emplace 构造 MyObject 对象
container.emplace(value);
}

std::emplace_back可以在容器内部直接构造对象,避免了额外的拷贝和移动操作,提高了性能。

使用 std::emplace_back 需要注意下面的一些事项:

  1. 理解参数: std::emplace_back允许你在容器的末尾构造元素,而不是传递一个已构造的对象。因此,要确保你提供的参数与元素的构造函数参数匹配,以便正确地构造对象。

  2. 避免不必要的拷贝和移动: 使用std::emplace_back时,不会发生拷贝或移动操作,因为元素是直接在容器内部构造的。这可以提高性能,但也意味着你不应该传递一个已经构造好的对象,而是提供构造对象所需的参数。

  3. 注意引用折叠: 当使用std::forward进行参数的完美转发时,要注意引用折叠的情况。确保传递参数时保持原始的左值/右值属性。

  4. 异常安全性: 在std::vector等动态容器中,std::emplace_back可能会触发重新分配内存。要确保在重新分配内存时不会导致资源泄漏或对象的不一致状态。推荐使用RAII(Resource Acquisition Is Initialization)等技术来保证异常安全性。

  5. 构造函数的异常: 如果元素的构造函数抛出异常,容器会保持原状,不会插入新元素。确保你的构造函数在异常发生时不会引起资源泄漏,并正确处理异常情况。

  6. 避免迭代器失效: 在插入元素时,要注意可能会导致迭代器失效,因为容器可能会重新分配内存。如果需要保存迭代器,请在插入操作之前保留或更新迭代器。

  7. 移动语义的使用: 如果参数为右值引用,确保你在移动构造时正确地使用std::move。要遵循移动语义的原则,确保源对象在移动后处于有效但未定义的状态。

  8. 了解容器的特性: 不同的容器(如std::vectorstd::liststd::deque等)可能有不同的行为,例如动态分配内存的频率和方式。了解容器的特性有助于更好地使用std::emplace_back

  • 构造函数初始化列表放在同一行或按四格缩进并排多行.

列表初始化示例:

// 一行列表初始化示范.
return {foo, bar};
functioncall({foo, bar});
pair<int, int> p{foo, bar}; // 当不得不断行时.
SomeFunction(
{"assume a zero-length name before {"}, // 假设在 { 前有长度为零的名字.
some_other_function_parameter);
SomeType variable{
some, other, values,
{"assume a zero-length name before {"}, // 假设在 { 前有长度为零的名字.
SomeOtherType{
"Very long string requiring the surrounding breaks.", // 非常长的字符串, 前后都需要断行.
some, other values},
SomeOtherType{"Slightly shorter string", // 稍短的字符串.
some, other, values}};
SomeType variable{
"This is too long to fit all in one line"}; // 字符串过长, 因此无法放在同一行.
MyType m = { // 注意了, 您可以在 { 前断行.
superlongvariablename1,
superlongvariablename2,
{short, interior, list},
{interiorwrappinglist,
interiorwrappinglist2}};

  

构造函数初始化列表示例:

// 如果所有变量能放在同一行:
MyClass::MyClass(int var) : some_var_(var) {
DoSomething();
} // 如果不能放在同一行,
// 必须置于冒号后, 并缩进 4 个空格
MyClass::MyClass(int var)
: some_var_(var), some_other_var_(var + 1) {
DoSomething();
} // 如果初始化列表需要置于多行, 将每一个成员放在单独的一行
// 并逐行对齐
MyClass::MyClass(int var)
: some_var_(var), // 4 space indent
some_other_var_(var + 1) { // lined up
DoSomething();
} // 右大括号 } 可以和左大括号 { 放在同一行
// 如果这样做合适的话
MyClass::MyClass(int var)
: some_var_(var) {}

  

另外说下 {} 和 () 的使用场合

  1. 初始化歧义:

    • 大括号 {} 初始化也会调用构造函数,但它在某些情况下会更加严格,会对可能的初始化歧义提出更高的要求,特别是在初始化列表和聚合类型上。
    • 小括号 () 初始化也调用构造函数,但在一些情况下可能更容易引发初始化的歧义。
  2. 类型安全性:

    • 大括号 {} 初始化通常更严格,不允许窄化转换,这可以提高类型安全性。
    • 小括号 () 初始化可能允许某些窄化转换,可能会降低类型安全性。
 示例:
#include <iostream>

class MyClass {
public:
explicit MyClass(int value) {
std::cout << "Constructor: " << value << std::endl;
}
}; int main() {
MyClass obj1(42); // 使用小括号初始化,显示调用构造函数
MyClass obj2{42}; // 使用大括号初始化,会调用构造函数,可能会对初始化歧义提出更高要求 int x(5.5); // 使用小括号,发生窄化转换,x的值为5
int y{5.5}; // 使用大括号,编译器会报错,不允许窄化转换 return 0;
} 

总而言之,初始化构造函数时可以使用小括号,而列表初始化尽量使用大括号,以避免歧义

3. 所有头文件都应该有 #define 保护来防止头文件被多重包含, 命名格式当是: <PROJECT>_<PATH>_<FILE>_H_ .

概述:为保证唯一性, 头文件的命名应该基于所在项目源代码树的全路径. 例如, 项目 foo 中的头文件 foo/src/bar/baz.h 可按如下方式保护:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_ 

头文件保护通过预处理器的条件编译(Conditional Compilation)来实现,具体原理如下:

  1. 当第一次包含头文件时,预处理器会检查该头文件中的宏是否已经定义。由于在开始时没有定义这个宏,条件 #ifndef 判断为真,预处理器会进入条件编译区块。

  2. 在进入条件编译区块后,宏 PROJECT_PATH_FILE_H_ 会被定义,这样就防止了多重包含。

  3. 当其他源文件也试图包含同一个头文件时,预处理器会再次检查宏是否已经定义。由于在前面的包含中已经定义了 PROJECT_PATH_FILE_H_ 宏,条件 #ifndef 判断为假,预处理器会忽略后续的头文件内容。

这种方式有效地避免了头文件的多重包含,因为在第一次包含时,宏被定义并且后续的包含都会被忽略。这样做的好处是:

  1. 避免重复定义: 多重包含可能导致重复定义的错误,而头文件保护可以确保每个头文件在一个编译单元中只包含一次。

  2. 提高编译速度: 如果没有头文件保护,重复包含会导致编译器不断重复解析同一个头文件,降低了编译速度。

  3. 避免依赖问题: 有些头文件可能定义了类型或者常量,多重包含可能导致不一致的定义,从而引发链接错误。

综上所述,头文件保护是一种重要的 C++ 编程实践,它确保了头文件的一致性、编译效率和链接正确性。

Google C++ 风格指南记录的更多相关文章

  1. 《Google 代码风格指南》

    <Google 代码风格指南> https://github.com/google/styleguide

  2. Google Java 风格指南(Google Java Style Guide)

    官方地址 google.github.io 本文档作为 Google 的 Java 编程语言源代码编码标准的完整定义.当且仅当它遵守此处的规则时,Java 源文件才被描述为 Google 风格. 前言 ...

  3. Google代码风格指南

    官网:https://github.com/google/styleguide 中文版:https://github.com/zh-google-styleguide/zh-google-styleg ...

  4. Google C++ 风格指南 命名约定 转

    命名约定 最重要的一致性规则是命名管理. 命名风格快速获知名字代表是什么东东: 类型? 变量? 函数? 常量? 宏 ... ? 甚至不需要去查找类型声明. 我们大脑中的模式匹配引擎可以非常可靠的处理这 ...

  5. 来自 Google 的 R 语言编码风格指南

    来自 Google 的 R 语言编码风格指南R 语言是一门主要用于统计计算和绘图的高级编程语言. 这份 R 语言编码风格指南旨在让我们的 R 代码更容易阅读.分享和检查. 以下规则系与 Google ...

  6. Google开源项目风格指南

    Google开源项目风格指南 来源 https://github.com/zh-google-styleguide/zh-google-styleguide Google 开源项目风格指南 (中文版) ...

  7. R 语言编码风格指南

    R 语言是一门主要用于统计计算和绘图的高级编程语言.这份 R 语言编码风格指南旨在让我们的 R代码更容易阅读.分享和检查.以下规则系与 Google 的 R 用户群体协同设计而成. 概要: R编码风格 ...

  8. Google C++编程风格指南

    作者:Hawstein 出处:http://hawstein.com/posts/google-cpp-style-guide.html 前言 越来越发现一致的编程风格的重要性,于是把Google的C ...

  9. Google C++编程风格指南 - 中文版

    Google C++编程风格指南 - 中文版 from http://code.google.com/p/google-styleguide/ 版本: 3.133原作者: Benjy Weinberg ...

  10. Google Java编程风格指南

    出处:http://hawstein.com/posts/google-java-style.html 声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Creative Comm ...

随机推荐

  1. [转帖]UTF8 和 AL32UTF8 的区别

    本文章向大家介绍UTF8 和 AL32UTF8 的区别,主要内容包括 .使用实例.应用技巧.基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下.  UTF8 和 AL32UTF8 ...

  2. [转帖]【redis】redis各稳定版本特性(更新到6.0版本)

    1.Redis2.6 Redis2.6在2012年正是发布,经历了17个版本,到2.6.17版本,相对于Redis2.4,主要特性如下: 1)服务端支持Lua脚本. 2)去掉虚拟内存相关功能. 3)放 ...

  3. [译]深入了解现代web浏览器(二)

    本文是根据Mariko Kosaka在谷歌开发者网站上的系列文章https://developer.chrome.com/blog/inside-browser-part2/ 翻译而来,共有四篇,该篇 ...

  4. 每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!

    写在开头 请聊一聊Java中方法的重写和重载? 这个问题应该是各大厂面试时问的最多的话题之一了,它们几乎贯穿了我们日常的开发工作,在过往的博客中我们多多少少都提到过重载与重写,而今天我们就一起来详细的 ...

  5. JS遍历树形数据

    树形数据结构遍历某个key值 深度优先遍历(DFS) let tree = [{ id: '1', name: '节点1', children: [{ id: '1-1', name: '节点1-1' ...

  6. 小程序之使用阿里字体图标 定义主题的颜色 控制首页标题的样式 如何使用组件 水平居中和垂直居中的方式 H5 关于上线后,

    项目搭建 1==> 需要创建的文件夹 styles 存放公共的样式 components 存放组件 lib第三方库的 utils 自己的帮助库 reques 自己的接口 2==>如何快速创 ...

  7. vue mixin混入 全局混入 局部混入

    <div id="app"> --{{nameName}} </div> // 全局混入 不需要注册 var m1 = Vue.mixin({ data() ...

  8. 【JS逆向百例】某音乐网分离式 webpack 非 IIFE 改写实战

    关注微信公众号:K哥爬虫,QQ交流群:808574309,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途 ...

  9. 分页sql大全

    一.排除Top分页法(自命名,非规范) 思想:所谓"排除Top分页",主要依靠"排除"和Top这个两大核心步骤.首先查询当前页码之前的数据,然后将该数据从总数据 ...

  10. C/C++ 反汇编:分析类的实现原理

    反汇编即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解.外挂技术.病毒分析.逆向工程.软件汉化等领域,学习和理解反汇编对软件调试.系统漏洞挖掘.内核原理及理解高级语言代码都有相当大的帮助, ...