- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup、Herb Sutter 等顶尖 C+ 专家创建的一份 C++ 指南、规则及最佳实践。旨在帮助大家正确、高效地使用“现代 C++”.
这份指南侧重于接口、资源管理、内存管理、并发等 High-level 主题。遵循这些规则可以最大程度地保证静态类型安全,避免资源泄露及常见的错误,使得程序运行得更快、更好.
文中提到的 GSL(Guidelines Support Library) 是 C++ 核心指南支持库 https://github.com/Microsoft/GSL 。
本节的规则反映了现代 C++ 的哲学/基本理念,贯穿整个 C++ 核心指南:
规则摘要:
这些基本理念是其他章节具体规则的理论基础.
以避免(可能无法发现的)错误结果或莫名其妙地程序崩溃 。
// 👎 容易出错
void increment1(int* p, int n)
{
for (int i = 0; i < n; ++i) ++p[i];
}
void use1(int m)
{
const int n = 10;
int a[n] = {};
// 可能是手误,或者 m<=n,但万一 m==20 ...
increment1(a, m);
}
use1() 中犯了个小错误,可能导致数据损坏或程序崩溃。形如 f(pointer, count) 这样形式的接口没有办法彻底避免 “out-of-range” 错误。如果我们检查下标越界,那也要等到访问 p[10] 的时候才能发现。可以改进代码,更早地进行检查:
void increment2(span<int> p)
{
for (int& x : p) ++x;
}
void use2(int m)
{
const int n = 10;
int a[n] = {};
// 可能是手误,或者 m<=n
increment2({a, m});
}
现在 m <= n 能在调用 increment2() 时就检查。如果本来就想用 n 作为边界,代码可以进一步简化(同时也消除了错误的可能):
void use3(int m)
{
const int n = 10;
int a[n] = {};
// 不需要重复元素个数
increment2(a);
}
不要重复检查同一个值,不要把结构化的数据作为 string 传递:
// 从 istream 中读日期
Date read_date(istream& is);
// 从 string 中提取日期
Date extract_date(const string& s);
// 操作日期
void user1(const string& date)
{
auto d = extract_date(date);
// ...
}
void user2()
{
Date d = read_date(cin);
user1(d.to_string());
}
user2() 中同一个日期被 Date 的构造函数校验了 2 次(一次在调用 read_date() 时,一次在调用 user1() -> extract_date() 时),并且原本结构化的 Date d 作为(非结构化的) string 传给了 user1() 。
额外的检查是有开销的。有时过早的检查效率不高,因为可能根本用不到这个值。或者只用到了部分,而只检查整个值的开销远大于检查用到部分的开销。类似地,不要增加改变接口复杂度的检查,例如不要在一个平均复杂度 O(1) 的接口中添加一个 O(n) 的检查.
即便资源消耗以极其缓慢的速度增加,最终也会耗尽资源,尤其是对长时间运行的程序来说.
void f(char* name)
{
FILE* input = fopen(name, "r");
// 👎 如果 something == true,文件句柄泄漏
if (something) return;
// ...
fclose(input);
}
应该使用 RAII
void f(char* name)
{
ifstream input {name};
// OK:不会泄露
if (something) return;
// ...
}
参见: 资源管理 R 篇 。
“泄漏”是指资源没有清理或者再也无法被清理。例如,在堆上分配一个对象,然后丢弃指向该对象的指针.
new
和 delete
fopen
, malloc
, strdup
) 因为这是 C++。但如果花费的时间和空间是为了达成特定目标(例如开发速度,资源安全或简化测试),那么并不算浪费.
“追求效率的另一个好处是,这个过程会迫使你更深入地理解问题。” —— Alex Stepanov 。
struct X {
char ch;
int i;
string s;
char ch2;
X& operator=(const X& a);
X(const X&);
};
X waste(const char* p)
{
if (!p) throw Nullptr_error{};
int n = strlen(p);
auto buf = new char[n];
if (!buf) throw Allocation_error{};
for (int i = 0; i < n; ++i) buf[i] = p[i];
// ... 操作 buffer ...
X x;
x.ch = 'a';
// give x.s space for *p
x.s = string(n);
// copy buf into x.s
for (gsl::index i = 0; i < x.s.size(); ++i)
x.s[i] = buf[i];
delete[] buf;
return x;
}
void driver()
{
X x = waste("Typical argument");
// ...
}
这是一个夸张的例子,但每一个错误(甚至更糟的)都在实际生产代码中出现过。结构体 X 的布局至少浪费了 6 个字节。显式声明的拷贝构造和拷贝赋值运算符抑制了移动操作,导致返回操作较慢(注意:这里不能保证 RVO 返回值优化)。对 buf 使用 new 和 delete 是多余的;如果真的需要一个局部字符串,应该使用局部 string 。还有几处代码,完全没有必要实现得那么复杂.
void lower(zstring s)
{
for (int i = 0; i < strlen(s); ++i) s[i] = tolower(s[i]);
}
这是一个真实生产代码的例子。在循环中的条件是 i < strlen(s) 。这个表达式在每次循环迭代时都会被计算,这意味着 strlen 必须在每次循环中遍历字符串来计算字符串长度。虽然字符串内容改变了,但 tolower 不会影响字符串的长度,所以最好在循环之外缓存字符串长度,而不是每次循环都重新计算长度.
一个浪费的例子并不会产生显著的影响,如果影响显著,通常可以轻易地发现并消除。但如果代码库中到处都是这样的代码,则很容易从量变到质变。这个规则的目的是在问题出现之前,消除 C++ 使用相关的大部分浪费。之后,我们可以考虑与算法和需求相关的浪费,但这超出了 C++ 核心指南的范围.
有许多更具体的规则,旨在实现“简单,消除不必要浪费”这个整体目标.
operator++()
或 operator--()
未使用的返回值,最好使用前置形式 见条款 Con: Constants and immutability 。
这里的低级(low-level)指的是代码的抽象层级较低,直接操作指针、malloc、realloc 等这样的低层函数 。
int sz = 100;
int* p = (int*) malloc(sizeof(int) * sz);
int count = 0;
for (;;) {
// ... read an int into x, exit loop if end of file is reached ...
// ... check that x is valid ...
if (count == sz)
p = (int*) realloc(p, sizeof(int) * sz * 2);
p[count++] = x;
// ...
}
这是一段低级、冗长、易错的代码:例如这里就没有考虑内存耗尽的问题。应该使用 vector:
vector<int> v;
v.reserve(100);
// ...
for (int x; cin >> x; ) {
// ... check that x is valid ...
v.push_back(x);
}
标准库和 GSL 是该思想的体现:vector、span、lock_guard、future 等关键抽象封装了低层的数组、union、类型转换等操作。通常库的设计/实现者远比我们更专业,花的时间也更多。我们应该使用库提供的高级抽象,而不是让低层细节搞乱我们的代码.
查找混乱的代码,例如 。
有的事情让机器来做更合适:计算机不会对重复的工作感到无聊和厌倦,而人类可以做一些更有价值的事情 。
运行静态代码分析工具来确保代码遵循某些规范:如 coverity、clang-tidy、clang-format 等 。
见 。
静态检查工具 。
并发工具 。
测试工具 。
还有许多其他类型的工具,如代码仓库、构建工具等,这些不在 C++ 核心指南范围内 。
不要依赖过于复杂、特殊的工具链,这会让可移植代码变得不可移植 。
使用设计/文档/支持良好的库可以节省时间和精力。无论是代码质量还是文档质量,通常要比自己写的好得多。库的开发维护成本是被所有用户平摊的(实际上大多数优秀的库都是可以免费使用的)。如果一个库有很多用户,也更容易保持更新并移植到新系统,并且相关的使用经验和知识也可以在其他项目复用,节约时间和精力.
std::sort(begin(v), end(v), std::greater<>());
除非你是排序算法的专家,并且有大量时间,否则你很难写出比上面那一行更正确、更快的排序代码.
使用标准库(或其他基础库)不需要理由;相反,不用标准库最好给出充分的理由.
最后此篇关于C++核心指南之C++哲学/基本理念(下)的文章就讲到这里了,如果你想了解更多关于C++核心指南之C++哲学/基本理念(下)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!