我记得首先在STL中学习矢量,经过一段时间后,我想在我的一个项目中使用bool矢量.在看到一些奇怪的行为并做了一些研究之后,我了解到一个bool矢量并不是真正的bool矢量.
在C++中还有其他常见的陷阱需要避免吗?
简短列表可能是:
通过使用共享指针来管理内存分配和清理,避免内存泄漏
使用资源获取初始化(RAII)习惯来管理资源清理 - 尤其是在存在异常的情况下
避免在构造函数中调用虚函数
尽可能采用极简主义编码技术 - 例如,仅在需要时声明变量,确定变量范围和尽可能早期设计.
真正理解代码中的异常处理 - 既包括您抛出的异常,也包括您可能间接使用的类抛出的异常处理.这在存在模板时尤为重要.
RAII,共享指针和极简主义编码当然不是特定于C++,但它们有助于避免在使用该语言进行开发时经常出现的问题.
关于这个主题的一些优秀书籍是:
有效的C++ - Scott Meyers
更有效的C++ - Scott Meyers
C++编码标准 - Sutter和Alexandrescu
C++常见问题解答 - Cline
阅读这些书对我来说最重要的是避免你所要求的那种陷阱.
首先,您应该访问获奖的C++常见问题解答.对陷阱有很多好的答案.如果您还有其他疑问,请访问##c++
上irc.freenode.org
的IRC.如果可以的话,我们很乐意帮助您.请注意,以下所有陷阱都是最初编写的.它们不仅仅是从随机来源复制而来.
delete[]
onnew
,delete
onnew[]
解决方案:执行上述操作会导致未定义的行为:一切都可能发生.理解你的代码及其作用,并始终delete[]
了解你new[]
,以及delete
那些new
不会发生的事情.
例外:
typedef T type[N]; T * pT = new type; delete[] pT;
你需要,delete[]
即使你new
,因为你新的阵列.因此,如果您正在与之合作typedef
,请特别小心.
在构造函数或析构函数中调用虚函数
解决方案:调用虚函数不会调用派生类中的重写函数.在构造函数或析构函数中调用纯虚函数是未定义的行为.
调用
delete
或delete[]
已经删除的指针
解决方案:为删除的每个指针指定0.调用delete
或delete[]
使用空指针什么都不做.
当要计算"数组"的元素数时,取指针的sizeof.
解决方案:当需要将数组作为指针传递给函数时,将元素数传递到指针旁边.如果你采用一个应该是一个数组的数组的sizeof,请使用此处提出的函数.
使用数组就好像它是一个指针.因此,
T **
用于二维阵列.
解决方案:请参阅此处了解它们为何不同以及如何处理它们.
写入字符串文字:
char * c = "hello"; *c = 'B';
解决方案:分配从字符串文字的数据初始化的数组,然后您可以写入它:
char c[] = "hello"; *c = 'B';
写入字符串文字是未定义的行为.无论如何,上述从字符串文字转换char *
为不推荐使用.因此,如果您提高警告级别,编译器可能会发出警告.
创建资源,然后在抛出某些东西时忘记释放它们.
解决方案:使用类似std::unique_ptr
或std::shared_ptr
由其他答案指出的智能指针.
在此示例中修改对象两次:
i = ++i;
解决方案:以上应该分配给i
的值i+1
.但是它的作用还没有定义.它不是递增i
和分配结果,而是i
在右侧改变.在两个序列点之间更改对象是未定义的行为.序列点包括||
,&&
,comma-operator
,semicolon
和entering a function
(非详尽的清单!).将代码更改为以下内容以使其正常运行:i = i + 1;
忘记在调用阻塞函数之前刷新流
sleep
.
解决方案:通过流式传输std::endl
而不是\n
通过调用来刷新流stream.flush();
.
声明函数而不是变量.
解决方案:问题出现是因为编译器解释了例如
Type t(other_type(value));
作为函数的函数声明t
返回Type
并具有other_type
被调用的类型的参数value
.你可以通过在第一个参数周围加上括号来解决它.现在你得到一个t
类型的变量Type
:
Type t((other_type(value)));
调用仅在当前转换单元(
.cpp
文件)中声明的自由对象的函数.
解决方案:标准没有定义跨不同翻译单元定义的自由对象(在命名空间范围内)的创建顺序.在尚未构造的对象上调用成员函数是未定义的行为.您可以在对象的翻译单元中定义以下函数,并从其他函数中调用它:
House & getTheHouse() { static House h; return h; }
这将按需创建对象,并在您调用函数时为您提供完全构造的对象.
在
.cpp
文件中定义模板,而在另一个.cpp
文件中使用它.
解决方案:几乎总是会出现类似的错误undefined reference to ...
.将所有模板定义放在标头中,以便在编译器使用它们时,它已经可以生成所需的代码.
static_cast
如果base是指向虚拟基类的指针(base); Derived
.
解决方案:虚拟基类是仅出现一次的基类,即使它在继承树中间接地由不同类继承多次.标准不允许执行上述操作.使用dynamic_cast来做到这一点,并确保您的基类是多态的.
dynamic_cast
如果base是非多态的(ptr_to_base);
解决方案:当传递的对象不是多态时,标准不允许向下转换指针或引用.它或其中一个基类必须具有虚函数.
让你的功能接受
T const **
解决方案:您可能认为这比使用更安全T **
,但实际上它会给想要通过的人带来头痛T**
:标准不允许它.它给出了一个简洁的例子,说明为什么不允许它:
int main() { char const c = ’c’; char* pc; char const** pcc = &pc; //1: not allowed *pcc = &c; *pc = ’C’; //2: modifies a const object }
总是接受T const* const*;
.
关于C++的另一个(封闭的)陷阱线索,所以寻找它们的人会发现它们,Stack Overflow是C++陷阱的问题.
有些必须有C++书籍,可以帮助您避免常见的C++陷阱:
有效的C++
更有效的C++
有效的STL
有效的STL书解释了bools问题的向量:)
Brian有一个很棒的列表:我会添加"始终标记单个参数构造函数(除非在极少数情况下需要自动转换)".
Scott Wheeler的网页C++陷阱涵盖了一些主要的C++陷阱.
不是特定的提示,而是一般指导原则:检查您的来源.C++是一种古老的语言,多年来它已经发生了很大的变化.最佳实践随之改变,但不幸的是,仍然存在大量旧信息.这里有一些非常好的书籍推荐 - 我可以再购买Scott Meyers C++的每一本书.熟悉Boost以及Boost中使用的编码风格 - 参与该项目的人员处于C++设计的最前沿.
不要重新发明轮子.熟悉STL和Boost,并尽可能使用他们的设施.特别是,使用STL字符串和集合,除非你有非常非常好的理由不这样做.快速了解auto_ptr和Boost智能指针库,了解在哪种情况下打算使用每种类型的智能指针,然后在任何地方使用智能指针,否则你可能会使用原始指针.您的代码将同样高效,并且更不容易出现内存泄漏.
使用static_cast,dynamic_cast,const_cast和reinterpret_cast代替C样式转换.与C风格的演员表不同,他们会告诉你,如果你真的要求不同类型的演员比你想象的要多.他们在视觉上脱颖而出,警告读者正在进行演员表演.
我已经多次提到它了,但Scott Meyers的书籍Effective C++和Effective STL在帮助C++方面真的值得用金.
想想看,Steven Dewhurst的C++ Gotchas也是一个出色的"来自战壕"的资源.他关于滚动你自己的例外以及它们应该如何构建的项目确实在一个项目中帮助了我.
两个陷阱,我希望我没有学到很多困难:
(1)默认情况下缓存很多输出(如printf).如果您正在调试崩溃的代码,并且您正在使用缓冲的调试语句,那么您看到的最后一个输出可能并不是代码中遇到的最后一个print语句.解决方案是在每次调试打印后刷新缓冲区(或完全关闭缓冲区).
(2)注意初始化 - (a)避免类实例为全局/静态; 并且(b)尝试将所有成员变量初始化为ctor中的某个安全值,即使它是一个微不足道的值,例如指针的NULL.
推理:无法保证全局对象初始化的顺序(全局变量包含静态变量),因此您最终可能会遇到似乎失败的代码,因为它依赖于在对象Y之前初始化对象X.如果没有显式初始化原始类型的变量,例如成员bool或类的枚举,在令人惊讶的情况下你会得到不同的值 - 再次,这种行为看起来非常不确定.
使用像C这样的C++.在代码中有一个创建和发布周期.
在C++中,这不是异常安全的,因此可能无法执行发布.在C++中,我们使用RAII来解决这个问题.
具有手动创建和释放的所有资源都应包装在对象中,以便这些操作在构造函数/析构函数中完成.
// C Code void myFunc() { Plop* plop = createMyPlopResource(); // Use the plop releaseMyPlopResource(plop); }
在C++中,这应该包含在一个对象中:
// C++ class PlopResource { public: PlopResource() { mPlop=createMyPlopResource(); // handle exceptions and errors. } ~PlopResource() { releaseMyPlopResource(mPlop); } private: Plop* mPlop; }; void myFunc() { PlopResource plop; // Use the plop // Exception safe release on exit. }