[C++] 编程实践之1: Google的C++代码风格2:作用域

C++:C++代码风格指南--Google(未完待续) 所谓「前置声明」(forward declaration)是类、函数和模板的纯粹声明,没伴随着其定义. 阅读详情

作用域

名字空间

鼓励在.cc文件内使用匿名名字空间。使用具名的名字空间时,其名称可基于项目名或者相对路径。禁止使用using指示(using-directive)。禁止使用内联命名空间(inline namespace)。

定义:

  • 名字空间将全局作用域细分为独立的,具名的作用域,可有效防止全局作用域的命名冲突。

优点:

  • 虽然类已经提供了(可嵌套的)命名轴线,名字空间在这一基础上又做了一层封装。
  • 内联命名空间会自动把内部的标识符放到外层的作用域,比如下面的代码中,X::Y::foo()与X::foo()彼此可代替。内联命名空间主要用来保持跨版本的API的兼容性。
namespace X {
    inline namespace Y {
        void foo();
    }
}

缺点:

  • 名字空间具有迷惑性,因为它们和类一样提供了额外的(可嵌套的)命名轴线。此外,它们不再受其声明所在的命名空间的限制。
  • 内联命名空间只在大型版本控制里有用。
  • 在头文件中使用匿名空间导致违背C++的唯一定义原则(One Definition Rule (ODR))。

结论:

  • 根据下文将要提到的策略合理地使用命名空间。
匿名名字空间
  • 在.cc文件中,允许甚至鼓励使用匿名名字空间,以避免运行时的命名冲突。然而,与特定类关联的文件作用域声明在该类中被声明为类型、静态数据成员或者静态成员函数,而不是匿名名字空间的成员。匿名空间结束时用注释 //namespace标识。
namespace {                                  // .cc文件中
    // 名字空间的内容无需知道
    enum {kUNUSED, kEOF, kERROR};            // 经常使用的符号
    bool AtEof() { return pos == kEOF; }     // 使用本名字空间内的符号EOF
}   // namespace
  • 不要在.h文件中使用匿名名字空间。
具名名字空间

具名的名字空间使用方式如下:

  • 用名字空间把文件包含gflags的声明/定义,以及类的前置声明以外的整个源文件封装起来,以区别于其它名字空间:
// .h 文件
namespace mynamespace {
// 所有声明都置于命名空间中
// 注意不要使用缩进
class MyClass {
    public:
    ...
    void Foo();
};
}
// .cc 文件
namespace mynamespace {
// 函数定义要置于命名空间中
void MyClass::Foo() {
    ...
}
} // namespace mynamespace

通常的.cc文件包含更多,更复杂的细节,比如引用其他名字空间的类等。

#include "a.h"
DEFINE_bool(someflag, false, "dummy flag");
class C;                    // 全局名字空间中类C的前置声明
namespace a { class A; }    // a::A的前置声明
namespace b {
...code for b...            // b中的代码
} // namespace b
  • 不要在名字空间std内声明任何东西,包括标准库的类前置声明。在std名字空间声明实体会导致不确定的问题,比如不可移植。声明标准库下的实体,需要包括对应的头文件。
  • 最好不要使用using指示,以保证名字空间下的所有名称都可以正常使用。
  • 在.cc文件,.h文件的函数、方法或者类中,可以使用using声明。
// 允许.cc 文件中使用using声明
// 如果在.h文件中,必须在函数、方法或者类的内部使用
using ::foo::bar;
  • 在.cc文件,.h文件的函数、方法或者类中,允许使用名字空间别名。注意在.h文件的别名对包含了该头文件的所有人可见,所以在公共头文件(在项目外可用)以及它们递归包含的其它头文件里,不要用别名。毕竟原则上公共API要尽可能地精简。
// 允许:.cc文件中
// .h文件的话,必须在函数,方法或者类的内部使用
namespace fbz = ::foo::bar::baz;

// 在 .h文件里
namespace librarian {
// 以下别名在所有包含了该头文件的文件中生效。
namespace pd_s = ::pipeline_diagnostics::sidetable;

inline void my_inline_function() {
    // namespace alas local to a function (or method).
    namespace fbz = ::foo::bar::baz;
    ...
}
}   // namespace librarian
  • 禁止使用内联命名空间。

嵌套类

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

定义:

  • 一个类的内部定义另外一个类;嵌套类也被成为成员类(member class)。
class Foo {
private:
    // Bar是嵌套类Foo中的成员类
    class Bar {
        ...
    };
}

优点:

  • 当嵌套类只被外围类使用时非常有用;把它作为外围类作用域内的成员,而不是去污染外部作用域的同名类。嵌套类可以在外围类中做前置声明,然后在.cc文件中定义,这样避免在外围类的声明中定义嵌套类,因为嵌套类的定义通常只与实现相关。

缺点:

  • 嵌套类只能在外围类的内部做前置声明。因此,任何使用了Foo::Bar*的指针的头文件不得不包含类Foo的整个声明。

结论:

  • 不要讲嵌套类定义成为公有,除非它们是接口的一部分,比如嵌套类含有某些方法的一组选项。

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

使用静态成员函数或名字空间内的非成员函数,尽量不要用裸的全局函数。

优点:

  • 某些情况下,非成员函数和静态成员函数是非常有用的,将非成员函数放在名字空间内将避免污染全局作用域。

缺点:

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

结论:

  • 有时,把函数的定义同类的实例脱钩时有益的,甚至是必要的。这样的函数可以被定义成为静态成员,或者非成员函数。非成员函数不应依赖于外部变量,应尽量置于某个名字空间内。相比单纯为了封装若干不共享任何静态数据的静态成员函数而创建类,不如使用 2.1 名字空间。
  • 定义在同一编译单元的函数,被其他编译单元直接调用可能会引入不必要的耦合和链接时依赖;静态成员函数对此尤其敏感。可以考虑提取到新类中,或者将函数置于独立库的名字空间内。
  • 如果你必须定义非成员函数,又只是在.cc文件中使用它,可使用匿名namespace或者“static”链接关键字(如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一开始就初始化

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

// 低效的实现
for (int i = 0; i < 1000000; ++i) {
    Foo f;                 // 构造函数和西沟函数分别调用1000000次!
    f.DoSomething(i);
}

静态和全局变量

禁止使用class类型的静态或者全局变量:它们会导致难以发现的bug和不确定的构造和析构函数调用顺序。不过constexpr变量除外,毕竟它们又不涉及动态初始化或者析构。

  静态生存周期的对象,即包括了全局变量,静态变量,静态类成员变量和函数静态变量,都必须是原生数据类型(POD: Plain Old Data):即int, char和float,以及POD类型的指针、数组和结构体。
  静态变量的构造函数、析构函数和初始化的顺序在C++中是不确定的,甚至是随着构建变化而变化,导致难以发现的bug,所以除了禁用类类型的全局变量,我们也不允许函数的返回值来初始化POD变量,除非该函数不涉及(比如getenv()或者getpid())任何全局变量。(函数作用域里的静态变量除外,毕竟它的初始化顺序是有明确定义的,而且只会在指令执行到她的声明那里才会发生)
  同理,全局和静态变量在程序中断时会被析构,无论所谓中断是从main()返回还是对exit()的调用。析构顺序正好与构造函数调用的顺序相反。但既然构造顺序未定义,那么析构顺序当然也就不定了。比如在程序结束时某静态变量已经被析构了,但代码还在跑——比如其它线程——并试图访问它且失败;再比如,一个静态string变量也许会在一个引用了前者的其它变量析构之前被析构掉。
  改善以上析构问题的办法之一是用quick_exit()来代替exit()并中断程序。它们的不同之处是前者不会执行任何析构,也不会执行atexit()所绑定的任何handles。如果您想在执行quick_exit()来中断执行某handler(比如刷新log),您可以把它绑定到_at_quick_exit()。如果您想在exit()和quick_exit()都用上该handler,都绑定上去即可。
  综上所述,我们只允许POD类型的静态变量,即完全禁用vector(使用C数组替代)和string(使用const char[])。
  如果您确实需要一个class类型的静态或者全局变量,可以考虑在main()函数或者pthread_once()内初始化一个指针并且永不回收。注意只能用raw指针,别用智能指针,毕竟后者的析构函数设计到上文指出的不定顺序问题。
  

总结

  1. .cc中的匿名名字空间可避免命名冲突,限定作用域,避免直接使用using关键字污染命名空间。
  2. 嵌套类符合局部使用原则,只是不能再其他头文件中前置声明,尽量不要public。
  3. 尽量不用全局函数和全局变量,考虑作用域和命名空间限制,尽量单独形成编译单元。
  4. 多线程中的全局变量(含静态成员变量)不要使用class类型(含STL容器),避免不明确行为导致的bug。
  5. 作用域的使用,除了考虑名称污染和可读性之外,主要是为了降低耦合,提高编译/执行效率。
  6. 注意using指示(using-directive)和using声明(using-declaration)的区别。
  7. 局部变量在声明的同时进行显式初始化,比起隐式初始化再赋值的两步过程要高效,同时也贯彻了计算机体系结构重要的概念局部性(locality)。
  8. 注意别在循环犯大量构造和析构的低级错误。
Google Cpp Guide 本文章主要记录内容讲解。未完成,还在持续更新… 阅读详情

相关推荐

汽车LED驱动芯片TPS92682-Q1 SPI接口配置与调试实战指南

SPI(串行外设接口)作为一种高速、全双工的同步串行通信总线,广泛应用于微控制器与外围设备之间的数据交换。其工作原理基于主从架构,通过时钟、数据线和片选信号实现设备间的精准时序控制。在汽车电子和工业控制领域,SPI因其简单高效、易于扩展的特性,成为配置复杂电源管理芯片、传感器和存储器的关键技术。通过SPI接口,工程师能够动态调整设备参数、实时读取状态信息,并实现复杂的故障诊断与保护策略,极大地提升了系统的灵活性与可靠性。本文以汽车级双通道LED恒流驱动器TPS92682-Q1为例,深入解析如何通过SPI接口

weixin_29185493的博客 288

Google C++ 风格指南 - 中文版

                    from    http://code.google.com/p/google-styleguide/  版本:3.133原作者:Benjy WeinbergerCraig SilversteinGregory EitzmannMark MentovaiTashana Landray翻译:YuleFoxyospaly项目主页:Google Style Gui...

weixin_33912445的博客 430

OpenAI Codex 科研实战:从文献整理到论文初稿的自动化流水线

在科研工作中,大量时间消耗在文献整理、数据清洗、结果反复排版等重复性任务上,而非真正的思考与创新。AI 编程代理作为一种能理解自然语言指令并直接操作文件、执行脚本的命令行工具,正逐渐改变科研工作流的组织方式。它基于云端大模型推理,本地无需 GPU,通过解析用户意图,自动完成代码生成、文件批量处理与任务拆解,将传统需要手动操作的工程过程转化为可验证、可追溯的指令执行。这类工具在学术场景中的价值尤为突出:科研人员可以将文献总结、数据预处理、统计分析与可视化出图交给代理完成,从而把精力聚焦在研究问题本身。Open

weixin_34318326的博客 458

using使用法总结

using有两种用法,一种是using declaration(申明),另一种是using directive(指示)。using declaration一次只引入命名空间的一个成员,使得在using declaration的作用域内,对该成员的访问不需要在前面加命名空间。如:using NewDevice::Exposure; using declaration可以用在不同的地方,如全局作用域或

有闻技术博客 1575

MATLAB 论文复现——面向电量-调频-容量市场的数据中心园区算力及电力资源规划

如何在保证数据中心服务质量的前提下,考虑数据中心负荷转移特性,并对其进行合理规划,对降低数据中心规划总成本及提升新型电力系统灵活性均有显著意义。然后,根据不同电力市场的结算规则,研究了数据中心园区参与电量-调频-容量市场的方式和经济收益。在此基础上,以综合成本最小化为目标,优化数据中心园区发电机、储能的建设容量以及服务器的建设数量。最后,以阿里巴巴公开运行数据为例,证明考虑多元电力市场能够大大降低数据中心规划成本,激励数据中心园区投资建设更多灵活性资源,为电网提供有效的调频服务和容量支撑。

sjr1478的博客 666

节卡Jaka Zu APP编程避坑指南:从频繁通讯错误到稳定Socket交互的调试心得

本文详细解析了节卡Jaka Zu APP编程中常见的通讯错误问题,并提供了从基础排查到高级调试的完整解决方案。通过优化网络配置、固件匹配、心跳机制和数据交互规范,开发者可实现稳定的Socket通讯,有效提升工业机器人操控的可靠性。文章特别针对v1.6.7.3版本的典型问题给出了实用调试技巧。

weixin_42639633的博客 321

Google 代码规范 C++ 解读一:头文件与作用域

C++ 有许多强大的特性,但强大也带来了复杂性,进一步我们的使代码更容易出错,更难阅读和维护。而且对于 C++,并没有一种强制的代码规范,我们经常能看到各种各样的代码规范,所以我在编程时总会有一种不自信感:无论写什么都感觉不正规、可能有 Bug,最终无从下笔或者不停修改,降低开发效率。 对于刚开始接触代码的同学,如果没有形成一个良好的代码规范,一旦形成手指肌肉记忆了,后面调整的成本会比较高。所以学习了解代码规范势在必行。......

weixin_45004203的博客 952

手把手教你用Cadence Virtuoso仿真一个5GHz CMOS分频器:从原理图到后仿,避开那些教科书上没写的坑

本文详细介绍了在Cadence Virtuoso环境中设计5GHz CMOS分频器的完整流程,从原理图设计到后仿真优化,特别关注实际工程中容易被忽视的关键问题。内容涵盖分频器核心指标解析、锁存器拓扑选择、接口设计匹配以及后仿真优化策略,为射频集成电路设计提供实用避坑指南。

weixin_26934905的博客 262

c++ 代码规范

http://tctop.wikispaces.com/Google%E4%BB%A3%E7%A0%81%E9%A3%8E%E6%A0%BC%E6%8C%87%E5%8D%97%E6%95%B4%E7%90%86 写代码时必须注重代码规范。养成良好的程序风格有助于提高代码的可维护性与可阅读性。  Google推出了自己的《Google C++ 风格指南》,在这份指南中各种C++编程中遇

Creek Shi 653

【Unity优化篇】| Unity3D场景 常用优化策略,遮挡剔除、层消隐距离技术 和 LOD多层次细节

本篇文章来简单介绍一下 Unity中几种常用3D场景优化策略 方法 对于整个项目来说,本文中出现的方法也只是冰山一角,慢慢积累学习吧! 内容包括遮挡剔除(Occlusion Culling)、层消隐距离技术 和 LOD多层次细节

努力前行,总会成为自己心中的那道光 1万+

基于STM32L4XX、HAL库的RTL8211FSI-CG以太网收发器(PHY)驱动程序设计

一、简介:RTL8211FSI-CG是Realtek公司生产的一款高性能、低功耗的单端口10/100M以太网收发器(PHY)。它通过RMII(Reduced Media Independent Interface)或MII接口与MAC控制器连接。

zhusl6688的博客 531

Google 开源项目风格指南阅读笔记(C++版)

虽说是编程风格指南,但是干货也不少,很多C++的实用技术在里面。头文件通常每一个.cpp文件都对应一个.h文件;#define保护所有头文件都应该使用#define防止头文件被多重包含,为保证唯一性,头文件的命名应该依据所在项目源码树的全部路径。能用前置声明的地方尽量不用#include当一个头文件被包含的同时也引入了新的依赖,一旦该头文件被修改,代码就会被重新编译,如果这个头文件又包含了其他头文

爱冒险的技术宅 2364

MTK-Android12开机应用自启需求-自定义原生Launcher3 主界面布局图标

MTK平台 Android12 版本 上实现 开机应用自启需求-自定义原生Launcher3 主界面布局图标。添加一个属性定义即可,如下:这里自定义一个属性目的是为了在加载Launcher3启动时候从属性获取需要自启动应用的包名,可以扩展动态替换 自启动包名。这里有两种方案,要么在Launcher3里面实现,要么在系统服务里面在开机时候启动第三方一次。这里强烈建议在Launcher3中去实现,体验效果更好。// 第一步:定义属性,// 第二步:在onCreate 方法中获取属性并启动应用。

十年饮冰,难凉热血 1376

C++ Google代码风格

C++ Google代码风格0 index(扉页)0.0 声明0.1 译者前言0.2 背景headers(头文件)1.1 Self-contained 头文件1.2. #define 保护1.3. 前置声明1.4. 内联函数1.5. #include 的路径及顺序译者(acgtyrant)笔记scoping(作用域)2. 作用域2.1. 命名空间2.2. 匿名命名空间和静态变量2.3. 非成员函数...

baidu_28926495的博客 1623

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

BAML C++ SDK 代码风格指南:Google 布局与标准库命名的工程实践

本文以 [baml_language/sdks/cpp/STYLE.md](https://link.gitcode.com/i/51b97241bd9f795a24c743a694047dac) 为骨架,系统讲解 BAML C++ SDK 的代码风格约定:布局(Layout)采用 Google 风格,命名(Naming)遵循 C++ 标准库的下划线风格,以及这套约定背后的源码级实现与例外条款。读

gitblog_00688的博客 389

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Google的C++编程规范总结

文章目录引言1 格式1.1 每行代码不多于 80 个字符1.2 使用空格(Space),而不是制表符(Tab)来缩进,每次缩进4个字符1.3 指针符号*,引用符号& 的位置,写在靠近类型的地方1.4 花括号位置1.5 if, for, while等语句就算只有一行,也强制使用花括号2 命名约定2.1 使用英文单词,不能夹着拼音2.2 总体上采用骆驼命名法2.3 名字不要加类型前缀2.4 类...

蒋成的博客 5262

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Abseil代码风格指南:Google C++代码规范的实践

在现代软件开发中,代码风格一致性是保证项目可维护性和可读性的关键因素。Google的C++代码规范被业界广泛认可,而Abseil库正是这一规范的完美实践。作为Google内部C++代码库的开源版本,Abseil不仅提供了高质量的C++组件,更展示了如何在实际项目中应用Google的代码风格规范。 本文将深入解析Abseil代码库中的风格实践,帮助开发者理解和应用这些最佳实践。 ## 命名约定:...

gitblog_00685的博客 984

Google C++编程风格指南

  [译]Google C++编程风格指南(八)[完] 2008年09月03日 星期三 17:06 原文地址:Google C++ Style Guide 规则之例外前面说明的编码习惯基本是强制性的,但所有优秀的规则都允许例外。1. 现有不统一代码(Existing Non-conformant 阅读全文>> 类别:c

skyremember的专栏 1234

C语言Google代码风格整理

想要写出整洁的代码就不得不遵循某种特定规范,在这篇文章中整理了C语言的Google代码规范。推荐刚刚接触代码的新手或是已经入门但是苦于代码编写风格的开发者阅读。

Reed_redd的博客 1197

LevelDB设计模式:Google C++代码风格深度解析 [特殊字符]

LevelDB是Google开发的一个高性能键值存储库,采用Google C++代码风格构建。这个开源项目展示了Google内部代码标准的最佳实践,为C++开发者提供了极好的学习范例。本文将深入解析LevelDB的设计模式和代码风格特点。 ## Google C++代码风格的核心原则 LevelDB严格遵循[Google C++代码风格指南](https://google.github.io/

gitblog_00077的博客 1093

《GOOGLE C++ 编程规范》读书笔记

GOOGLE C++ 规范读书笔记 1、头文件 1、使用#define避免头文件被重复包含,定义的宏为全路径+文件名; 2、能使用前置声明的地方尽量不使用#include。即如果不需要访问类定义的情况下,使用类的前置声明即可;需要类定义时,则必须得包含头文件。 3、只有当函数只有10行甚至更少时才将其定义为内联函数;内联包含循环或switch语句的函数常常是得不尝失(除非在大多数情况下

pointersky的专栏 989

google代码风格(C++)

from    http://code.google.com/p/google-styleguide/   版本:   3.133 原作者: Benjy Weinberger Craig Silverstein Gregory Eitzmann Mark Mentovai Tashana Landray 翻译: YuleFox yospaly 项目主页:

昨天的你 4010

终极指南:掌握Google C++代码风格规范的10个核心技巧

Google C++代码风格规范是业界公认的C++编程标准,为开发者提供了一套完整的代码编写指南。作为C++程序员,遵循这套规范不仅能提升代码质量,还能让团队协作更加顺畅。本文将通过10个关键技巧,带你从规范到实践全面掌握Google C++代码风格。🚀 ## 📝 为什么需要代码风格规范? 在大型项目开发中,统一的代码风格至关重要。Google C++规范强调**可读性优先**、**一致性

gitblog_00338的博客 409

[C++] 编程实践之1: Google的C++代码风格4:来自Google的奇技

来自Google的奇技  Google用了很多自己实现的技巧/工具使C++代码更加简装,我们使用C++的方式可能和你在气他地方见到的有所不同。所有权与智能指针 动态分配出的对象最好有单一且固定的所有主(owner),且通过智能指针传递所有权(ownership)。 定义: 所有权是一种登记/管理动态内存和其它资源的技术。动态分配出的对象的所有主是一个对象或者函数,后者负责确保当前者无用时就自动销

魔豆(Magicbean)的博客 628

向google学习良好的C++代码风格-(1)概述

第2学堂-专栏 3510

Google C++编程风格指南:cpplint报错信息解决汇总

Google C++编程风格指南:cpplint报错信息解决汇总

LF__plus的博客 2602
上一篇: [C++] 编程实践之1: Google的C++代码风格1:头文件
下一篇: [C++] 编程实践之1: Google的C++代码风格3:类
魔豆Magicbean
博客等级 码龄19年 146粉丝 742原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值