C++ 编码文化 - 第 I 部分
一篇关于 C++ 编码风格、实践和纪律的文章。
引言
用 C++ 编码是一种艺术,一种激情。Java 等许多其他语言都吸收了 C++ 的结构,因为 C++ 很优雅。在我的文章中,我将讨论 C++ 编码的许多风格、实践和纪律。我已将文章分成几个章节,本章将介绍如何优雅地编码以消除警告。
消除编译器警告
每次构建项目时,您都必须养成使其没有警告的习惯。解决警告始终是良好的做法,因为懒于解决它们或仅仅满足于代码能够正常工作将导致将来的危险。成功的构建应该是没有警告的。在本章中,我将讨论一些 Visual C++ 编译器产生的常见警告以及如何解决它们,以便我们能够成功构建。解决警告的方法不是关闭它,而是几乎对代码进行微小的修改,否则它可能成为运行时危险的潜在威胁。并非所有警告在运行时都是危险的,但它们总是会留下瑕疵。
1. 已定义但从未使用的变量
如果定义的变量未使用(即不需要),则将其删除。但这并非总是可行。让我们通过示例来理解。
void func() { // warning: "variable 'lv' is defined but never used" long lv; }
现在看看这种情况。这里我们调用一个可能抛出异常的 SomeFunc 方法。如果抛出异常,我们就会捕获它以安全地处理情况,然后仅输出一个跟踪消息。但我们收到一个警告,说“e”未使用。我们如何解决这个问题?只需将变量用作表达式。
void Fun() { try { SomeFunc(); } catch(MyException& e) { // warning: "variable 'e' is defined but never used" // Solution 1 e; // Solution 2 - Use just MyException & without the e in the catch declaration above. // Solution 3 - use UNREFERNCED_PARAMETER(e); OutputDebugString("Exception Occurred"); } }
寓意:始终知道您已声明了一个变量,以及您在代码中如何以及何处使用它。
2. 未初始化的变量
很多时候,我们声明 int i 或 long k,然后直接在循环或某些“if”语句中使用它们,这是灾难性的。始终将变量初始化为最适合情况的默认值。一个 int i 不一定总是初始化为 0,因为我可能要对大小为 20 的数组进行反向扫描。
寓意:始终知道您已声明了一个变量,以及您在代码中如何以及何处使用它。
3. 有符号/无符号不匹配
我曾无数次收到此警告。例如,程序员的自然倾向是将数字存储为 int。
unsigned long GetMyNumber() { long lv = 100; . . . return lv; } void main() { long x = 10; long y = 20; // signed/unsigned warning if ( x == GetMyNumber()) { } // signed/unsigned warning else if ( y == GetMyNumber() ) { } }
更改所比较变量的类型,以使类型匹配。如果情况不适用,请使用显式的 static_cast。C++ 中的 cast 运算符是非常好的风格和纪律。在上述情况下,编译器会隐式转换并发出警告,因为:
int c = -2; unsignec int uc = 255; if ( c < uc ) { cout << "Less Than"; } else { cout << "Not Less Than"; }
以上情况将打印“不小于”。
寓意:了解变量的类型和内容代表什么,并相应地操作它们。
4. 缺少返回值
考虑以下代码。
// compiler warning - missing return statement int missingret(int age) { if ( age <= 10 ) { return 1; } if ( age > 10 && age <= 50 ) { return 2; } if ( age > 50 && age < 500 ) { return 3; } }
对于您的员工程序来说,上述逻辑可能没问题,该程序规定任何员工的寿命不得超过 500 年。尽管如此,编译器不会相信它,并会警告您“如果一个员工活了 600 岁,我该返回什么?”。我们可能确定程序执行期间的控制流永远不会到达函数末尾,但编译器不是,我们需要通过在函数末尾提供 return 来说服它。最终,编译代码的是编译器,而不是我们,所以如果您想以这种方式编写优雅的代码,最好这样做:
// compiler warning - missing return statement int missingret(int age) { int nRetVal = 0; if ( age <= 10 ) { nRetVal = 1; } if ( age > 10 && age <= 50 ) { nRetVal = 2; } if ( age > 50 && age < 500 ) { nRetVal = 3; } return nRetVal; }
寓意:永远不要假设一个员工的寿命不会超过 500 年。确保所有控制路径都返回正确的值,并且不要在运行时假设未知值。
5. 不可达代码
这是一个简单而众所周知的警告。
int func() { return 2; cout << "How can i reach here ??"; }
上述代码中的 cout 语句不可达,因为我们无论如何都会返回 2。上述代码是一个说明问题的简单代码片段。在涉及许多循环和决策语句的复杂逻辑的函数中,我们会遇到这种情况,在其中,我们遗漏了导致大量代码不可达的内容。例如,如果一个 if 块在
寓意:当函数有多个 return 语句时,请彻底审查代码。
6. 未使用的函数参数
如果参数确实未使用,但只是出于您不记得的原因而设置,则最好将其删除。
// warning for b - better remove it. int ManipulateString(const std::string& sText, bool b = false) { sText.append("_Modified"); return 0; }
但有时在我的项目中,我也会遇到这种情况,即我必须为函数原型化一个参数,但该参数只在我的下一个版本中有用。在这种情况下,语法会奏效。
int ManipulateString(const std::string& sText, bool /*b*/ = false) { sText.append("_Modified"); return 0; }
稍后
int ManipulateString(const std::string& sText, bool b = false) { if ( b == true ) { sText.append("_Modified"); } else { sText.append("_Modified_New Version"); } return 0; }
寓意:确保您拥有一个参数是因为您现在或将来需要它。如果现在不需要,请删除函数参数的名称。
7. 第三方文件中的警告
我的天,这在我的项目中一直是一场战斗。我们在项目中使用第三方开发头文件,并从这些开发头文件中收到大量警告。在检查第三方头文件中的代码时,您可能会发现我们上面讨论的一些警告情况,但我们不应该更改它们。在这种情况下,我遵循 Herb Sutter 的建议。用您自己的版本包装文件,该版本包含原始头文件,并仅选择性地关闭该范围内的嘈杂警告,然后在项目的其余部分中包含您的包装器。
// File: myproj/MyTrace.h -- wraps third party TPTrace.h // Now include MyTrace.h where ever you need TPTrace.h. Don't use TPTrace.h directly. // When they fix it we'll remove the pragmas below, but this header will still exist. #pragma warning(push) // disable for this header only #pragma warning(disable:4512) #pragma warning(disable:4180) #include "ThirdParty/TPTrace.h" #pragma warning(pop) // restore original warning level
结论
因此,在本章中,我们学习了如何稍微改写我们的代码并避免警告。这会让代码审查者感受到代码的优雅,您也可以确保他正确理解您在代码中所做的尝试。毕竟,软件业务有一半的时间是这样运作的。在下一章中,让我们看看其他有助于我们编写良好代码的实践。
“永远写出好代码。写出坏代码会让你感觉不好。” - Herb Sutter。
历史
§ 初版 – 2005 年 4 月 14 日。
§ 更新 – 2005 年 4 月 20 日 – 更新了读者评论。