题
我记得第一个学习有关矢量在限额以及在一段时间之后,我想用一矢量的弹球我的一个项目。之后看到一些奇怪的行为和做一些研究,我了解到 一矢量的弹球是不是真的一矢量的弹球.
是否有任何其他常见的缺陷,以避免在C++?
解决方案
一个短名单可能是:
- 避免存储器泄漏通过使用共用针管理内存分配和清理
- 使用 资源的获取是初始化 (RAII)语来管理资源清理-特别是在存在例外情况
- 避免调用虚拟职能的构造
- 采用简编码技术在可能的--例如,声明变量只在需要的时候,范围变量,以及早期的设计,在可能的情况。
- 真正了解的例外处理,在你的代码-两方面的异常你扔,以及那些引发的课程你也可以使用的是间接的。这是特别重要的存在的模板。
RAII,共享指针,并简编码是当然没有具体要C++,但它们有助于避免出现问题,经常出现在发展中的语言。
一些优秀的书籍,在这个问题是:
- 有效C++-斯科特*梅尔
- 更有效C++-斯科特*梅尔
- C++编码标准的萨特&Alexandrescu
- C++FAQs-Cline
阅读这些书籍,帮助我比其他任何避免这种陷阱,你要求。
其他提示
陷阱中减少了他们的重要性
首先,你应该访问获奖 C++常见问题.它具有许多良好的答案的陷阱。如果你有进一步的问题,访问 ##c++
上 irc.freenode.org
在 因诺琴蒂研究中心.我们很高兴帮你,如果我们可以。注意以下所有缺陷,最初编写的。他们不仅仅是复制从随机的来源。
delete[]
上new
,delete
上new[]
解决方案:做上产生不确定的行为:一切都可能发生。懂你的代码和它做什么,总是 delete[]
什么你 new[]
, , delete
什么你 new
, 然后就不会发生。
异常:
typedef T type[N]; T * pT = new type; delete[] pT;
你需要 delete[]
即使你 new
, 因为你新的'ed阵列。所以如果你的工作 typedef
, ,采取特别的照顾。
叫一个虚拟的功能在一个构造或析构
解决方案:叫一个虚拟的功能不会叫的首要功能衍生出类。叫一个 纯粹的虚拟功能 在一个构造或desctructor是不确定的行为。
叫
delete
或delete[]
在一个已经被删除的指针
解决方案:分配0到每一个指你删除。叫 delete
或 delete[]
在null-指什么都不做。
如果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);
如果基地是一个指向一个虚拟的基类的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++,使人们寻找他们会找到他们,是堆溢出问题 C++的陷阱.
布莱恩有一个很大的清单:我想添加"总是标记单参数的构造明确的(除了这些罕见的情况下你想自动铸件)。"
不真的是一个具体的尖端,但是一般准则:检查你的来源。C++是一个古老的语言,它已经改变了很多年。最好的做法已经改变,但不幸的是,仍然有很多的古老的信息。已有一些非常好的建议书在这里-我可以第二次购买的每一个斯科特*梅尔C++的书籍。熟悉提高和编码使用的样式在提高人民参与该项目是在刀刃上的C++的设计。
不要重新发明车轮。熟悉STL和提高,并使用他们的设施,尽可能滚你自己的。特别是,使用STL串和集,除非你有一个非常,非常好的理由不。要知道auto_ptr和提升的明智的指针,图书馆很好,了解在何种情况下的每个类型的智能指针的目的是要使用,然后使用智能指针无处不在,你可能以其他方式使用原指针。你的码将只是作为有效和很多不容易存储器的泄漏。
使用static_cast,dynamic_cast,const_cast,并reinterpret_cast,而不是C-风格转换。与C-风格蒙上他们会让你知道如果你真的要求不同类型的铸比你认为你所要求的。和他们站出来viisually,提醒读者,一个铸造成的。
该网页 C++的陷阱 斯科特*惠勒涵盖的一些主要C++的陷阱。
两个陷阱,我希望我没有学习困难的方式:
(1)大量的输出(例如printf)缓冲默认。如果你在调试崩溃的代码,就使用缓冲的调试声明,最后输出你可以看到 不 真是最后的打印的发言中所遇到的代码。该方案是齐的缓冲器后的每一个调试print(或关闭缓冲共).
(2)小心初始化(a)避免类实例如全局/统计;(b)尝试来初始化的所有成员变量的一些安全价值的一个构造函数,即使是一个微不足道的价值,例如空为指针。
推理:订购的全球对象的初始化是不能保证(globals包括静态变量),这样就可以结束了代码,似乎失败以非确定性的方式,因为它取决于对象X被初始化之前的目Y。如果你不明确的初始化的一个原型可变的,例如一个成员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.
}
这本书 C++陷阱 可以证明是有用的。
这里有几个坑我的不幸落入。所有这些具有良好的原因而我只能理解被咬伤后的行为让我感到惊讶。
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`
最重要的缺陷开始的开发是为了避免混淆之间C和C++。C++永远不应该被视为一个不仅能更好地C或C类,因为这个李子的力量和可以使它更危险(特别是在使用存储在C)。
检查了 boost.org.它提供了很多额外的功能,尤其是他们的明智指针的实现。
PRQA有 一个很好的和免费C++编码的标准 基于书籍斯梅尔,Bjarne Stroustrop和草药萨特.它带来的所有这些信息在一起的一份文件。
- 不读 C++常见问题的精简.这解释了很多坏(和良好!) 实践。
- 不使用 提高.你会拯救你自己一个很大的挫折通过利用提高,在可能的情况。
谨慎使用智能指针和容器类。
避免 伪课程和准类...重复设计基本上是这样。
忘了定义的基类析构虚拟的。这意味着叫 delete
在基*不会最终毁灭的派生部分。
保持名字空间的直接(包括结构、课,名字空间,并使用).那是我最沮丧的时的程序,只是不进行编译。
乱,直接使用的指针很多。相反,使用RAII几乎所有的东西,确保当然,你使用权的明智的指针。如果你写的"删除"以外的任何地方的一个手柄或指针型类,你很有可能这样做是错误的。
读的书 C++的问题:避免常见的问题编码的设计.
Blizpasta.这是一个巨大的一个,我看到了很多...
初始化的变量是一个巨大的错误,学生的地雷。很多Java人们忘记,只是说"int反"不设置计数为0。因为你必须确定变量在h文件(和初始化它们在构造/设置的对象),很容易忘记。
关通过一个错误
for
循环/阵列的访问。不适当地清洁目的代码时,巫术的开始。
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
}
我以为我永远不会犯这种错误,但我真的这么做了。
论文/文章 指针,参考资料和价值观 是非常有用的。它谈判避免避免的缺陷和良好做法。你可以浏览整个网站太多,其中包含的编程技巧,主要是用C++。
我花了很多年这样做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);
}