我记得第一次学习STL中的向量,过了一段时间,我想为我的一个项目使用布尔向量。在看到一些奇怪的行为并进行了一些研究之后,我了解到bool的向量实际上并不是bool的向量。
在C ++中还有其他需要避免的常见陷阱吗?
我记得第一次学习STL中的向量,过了一段时间,我想为我的一个项目使用布尔向量。在看到一些奇怪的行为并进行了一些研究之后,我了解到bool的向量实际上并不是bool的向量。
在C ++中还有其他需要避免的常见陷阱吗?
Answers:
简短列表可能是:
RAII,共享指针和最低限度的编码当然不是特定于C ++的,但是它们有助于避免使用该语言进行开发时经常出现的问题。
关于此主题的一些优秀书籍包括:
读这些书对我有最大的帮助,避免了您要问的那种陷阱。
首先,您应该访问屡获殊荣的C ++常见问题解答。对于陷阱,它有很多好的答案。如果您还有其他疑问,请访问##c++
上irc.freenode.org
的IRC。如果可以的话,我们很乐意为您提供帮助。请注意,以下所有陷阱均是最初编写的。它们不只是从随机来源复制而来。
delete[]
上new
,delete
上new[]
解决方案:对未定义的行为进行上述操作会导致一切皆有可能。了解您的代码及其作用,始终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<Derived*>(base);
如果base是指向的虚拟基类的指针Derived
。
解决方案:虚拟基类是仅发生一次的基类,即使它被继承树中的不同类间接继承了多次。本标准不允许执行上述操作。使用dynamic_cast来做到这一点,并确保您的基类是多态的。
dynamic_cast<Derived*>(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 ++是一门古老的语言,多年来已经发生了很大的变化。最佳做法已随之改变,但不幸的是,那里仍然有很多旧信息。这里有一些很好的书籍推荐-我可以第二次购买每一本Scott Meyers C ++书籍。熟悉Boost和Boost中使用的编码样式-与该项目相关的人员处于C ++设计的最前沿。
不要重新发明轮子。熟悉STL和Boost,并尽可能使用它们的功能。特别是,除非有非常非常好的理由,否则请使用STL字符串和集合。很好地了解auto_ptr和Boost智能指针库,了解在哪种情况下打算使用每种类型的智能指针,然后在其他可能使用原始指针的地方使用智能指针。您的代码将同样高效,并且不易发生内存泄漏。
使用static_cast,dynamic_cast,const_cast和reinterpret_cast而不是C样式强制转换。与C样式的强制类型转换不同,它们将让您知道您是否真正要求的类型与您期望的类型不同。他们在视觉上脱颖而出,警告读者演员阵容正在发生。
我希望我没有学到的两个难题:
(1)默认情况下,很多输出(例如printf)被缓冲。如果您正在调试崩溃的代码,并且正在使用缓冲的调试语句,那么您看到的最后一个输出可能实际上并不是代码中遇到的最后一个打印语句。解决方案是在每次调试打印后刷新缓冲区(或完全关闭缓冲区)。
(2)注意初始化-(a)避免将类实例作为全局变量/静态变量;(b)尝试将所有成员变量初始化为ctor中的某个安全值,即使该值是微不足道的值(例如NULL)也是如此。
推理:不能保证全局对象初始化的顺序(全局变量包含静态变量),因此您最终可能会导致代码不确定性地失败,因为它取决于对象X在对象Y之前被初始化。如果您未明确初始化a基本类型的变量,例如类的成员bool或枚举,在令人惊讶的情况下,您将最终获得不同的值-同样,该行为似乎非常不确定。
我已经提到过几次了,但是Scott Meyers的书《Effective C ++》和《Effective STL》确实很有价值,因为它们可以帮助C ++。
想到这一点,史蒂文·德赫斯特(Steven Dewhurst)的C ++ Gotchas也是一个出色的“来自战trench”的资源。他的有关滚动自己的异常以及如何构造异常的项目确实在一个项目中对我有所帮助。
与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.
}
C ++ Gotchas这本书可能很有用。
这是我不幸碰到的几个陷阱。所有这些都有充分的理由,只有在被令我惊讶的行为咬伤之后,我才明白。
virtual
构造函数中的函数不是。
不要违反ODR(一个定义规则),这就是匿名名称空间的用途(除其他外)。
成员的初始化顺序取决于声明它们的顺序。
class bar {
vector<int> vec_;
unsigned size_; // Note size_ declared *after* vec_
public:
bar(unsigned size)
: size_(size)
, vec_(size_) // size_ is uninitialized
{}
};
默认值和virtual
具有不同的语义。
class base {
public:
virtual foo(int i = 42) { cout << "base " << i; }
};
class derived : public base {
public:
virtual foo(int i = 12) { cout << "derived "<< i; }
};
derived d;
base& b = d;
b.foo(); // Outputs `derived 42`
PRQA具有出色的免费C ++编码标准,该标准基于Scott Meyers,Bjarne Stroustrop和Herb Sutter的著作。它将所有这些信息汇总到一个文档中。
使用智能指针和容器类时要小心。
阅读《C ++陷阱:避免编码和设计中的常见问题》一书。
static_cast
在虚拟基类上贬低
并非如此……现在是关于我的误解:我认为A
以下内容实际上是一个虚拟基类,实际上不是;根据10.3.1,它是一个多态类。static_cast
在这里使用似乎很好。
struct B { virtual ~B() {} };
struct D : B { };
总之,是的,这是一个危险的陷阱。
在取消引用指针之前,请务必先检查它。在C语言中,通常可以指望在取消引用错误指针时发生崩溃。在C ++中,您可以创建一个无效的引用,该引用将在远离问题根源的位置崩溃。
class SomeClass
{
...
void DoSomething()
{
++counter; // crash here!
}
int counter;
};
void Foo(SomeClass & ref)
{
...
ref.DoSomething(); // if DoSomething is virtual, you might crash here
...
}
void Bar(SomeClass * ptr)
{
Foo(*ptr); // if ptr is NULL, you have created an invalid reference
// which probably WILL NOT crash here
}
目的是(x == 10)
:
if (x = 10) {
//Do something
}
我以为自己永远不会犯这个错误,但实际上我最近才犯过。
if (x == y)
我花了很多年从事C ++开发。我对几年前遇到的问题做了简短的总结。符合标准的编译器不再是真正的问题,但我怀疑所概述的其他陷阱仍然有效。
#include <boost/shared_ptr.hpp>
class A {
public:
void nuke() {
boost::shared_ptr<A> (this);
}
};
int main(int argc, char** argv) {
A a;
a.nuke();
return(0);
}