我应该避免哪些C ++陷阱?

时间:2008-08-27 15:03:00

标签: c++ stl

我记得首先在STL中学习矢量,过了一段时间,我想在我的一个项目中使用bool矢量。在看到一些奇怪的行为并做了一些研究之后,我学会了a vector of bools is not really a vector of bools

在C ++中是否还有其他常见的陷阱?

29 个答案:

答案 0 :(得分:76)

简短列表可能是:

  • 通过使用共享指针管理内存分配和清理来避免内存泄漏
  • 使用Resource Acquisition Is Initialization(RAII)习惯用法来管理资源清理 - 尤其是在出现异常的情况下
  • 避免在构造函数中调用虚函数
  • 尽可能采用极简主义编码技术 - 例如,仅在需要时声明变量,确定范围变量和尽可能早期设计。
  • 真正理解代码中的异常处理 - 既包括您抛出的异常,也包括您可能间接使用的类抛出的异常处理。这在存在模板时尤为重要。

RAII,共享指针和极简主义编码当然不是特定于C ++,但它们有助于避免在使用该语言进行开发时经常出现的问题。

关于这个主题的一些优秀书籍是:

  • 有效的C ++ - Scott Meyers
  • 更有效的C ++ - Scott Meyers
  • C ++编码标准 - Sutter& Alexandrescu的
  • C ++常见问题解答 - Cline

阅读这些书对我来说最重要的是避免你所要求的那种陷阱。

答案 1 :(得分:51)

答案 2 :(得分:16)

有些必须有C ++书籍,可以帮助您避免常见的C ++陷阱:

Effective C++
More Effective C++
Effective STL

有效的STL书解释了bools问题的载体:)

答案 3 :(得分:12)

Brian有一个很棒的列表:我会添加“始终标记单个参数构造函数(除非在极少数情况下需要自动转换)。”

答案 4 :(得分:8)

Scott Wheeler的网页 C++ Pitfalls 涵盖了一些主要的C ++陷阱。

答案 5 :(得分:8)

不是特定的提示,而是一般指导原则:检查您的来源。 C ++是一种古老的语言,多年来它已经发生了很大的变化。最佳实践随之改变,但不幸的是,仍然存在大量旧信息。这里有一些非常好的书籍推荐 - 我可以再购买Scott Meyers C ++的每一本书。熟悉Boost以及Boost中使用的编码风格 - 参与该项目的人员处于C ++设计的最前沿。

不要重新发明轮子。熟悉STL和Boost,并尽可能使用他们的设施。特别是,使用STL字符串和集合,除非你有非常非常好的理由不这样做。快速了解auto_ptr和Boost智能指针库,了解在哪种情况下打算使用每种类型的智能指针,然后在任何地方使用智能指针,否则你可能会使用原始指针。您的代码将同样高效且不易出现内存泄漏。

使用static_cast,dynamic_cast,const_cast和reinterpret_cast代替C风格的强制转换。与C风格的演员表不同,他们会告诉你,如果你真的要求不同类型的演员比你想象的要多。并且它们在视觉上脱颖而出,提醒读者正在进行演员表。

答案 6 :(得分:6)

使用像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.
}

答案 7 :(得分:6)

我已经提过了几次,但Scott Meyers的书籍Effective C++Effective STL在帮助C ++时非常值得用金。

想想看,Steven Dewhurst的C++ Gotchas也是一个出色的“来自战壕”的资源。他关于推销自己的例外以及如何构建它们的项目确实在一个项目中帮助了我。

答案 8 :(得分:6)

我希望我没有学到很多困难的两个问题:

(1)默认情况下缓存很多输出(如printf)。如果您正在调试崩溃的代码,并且您正在使用缓冲的调试语句,那么您看到的最后一个输出可能 not 确实是代码中遇到的最后一个print语句。解决方案是在每次调试打印后刷新缓冲区(或完全关闭缓冲区)。

(2)注意初始化 - (a)避免类实例为全局/静态;并且(b)尝试将所有成员变量初始化为ctor中的某个安全值,即使它是一个微不足道的值,例如指针为NULL。

推理:无法保证全局对象初始化的顺序(全局变量包含静态变量),因此您可能最终得到的代码看起来很不确定,因为它依赖于在对象Y之前初始化对象X.如果不是显式初始化一个原始类型的变量,例如一个成员bool或一个类的枚举,你会在令人惊讶的情况下得到不同的值 - 再次,这种行为看起来非常不确定。

答案 9 :(得分:4)

这本书 C++ Gotchas 可能有用。

答案 10 :(得分:4)

以下是我不幸陷入的几个坑。所有这些都有充分的理由,在被我感到惊讶的行为所困扰之后我才明白。

  • virtual在构造函数aren't中运行。

  • 不要违反ODR (One Definition Rule),这就是匿名命名空间的用途(除其他外)。

  • 成员的初始化顺序取决于它们的声明顺序。

    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`
    

答案 11 :(得分:3)

  1. 未阅读C++ FAQ Lite。它解释了许多不好(和好!)的做法。
  2. 不使用Boost。在可能的情况下利用Boost,你将为自己节省很多挫折。

答案 12 :(得分:3)

根据Scott Meyers,Bjarne Stroustrup和Herb Sutter的书籍,PRQA有an excellent and free C++ coding standard。它将所有这些信息整合在一个文档中。

答案 13 :(得分:3)

初学者最重要的陷阱是避免C和C ++之间的混淆。 C ++不应该只被视为一个更好的C或C类,因为它会削弱它的能力,甚至可以使它变得更危险(特别是当使用C中的内存时)。

答案 14 :(得分:3)

结帐boost.org。它提供了许多附加功能,尤其是它们的智能指针实现。

答案 15 :(得分:2)

避免pseudo classes and quasi classes ......过度设计。

答案 16 :(得分:2)

忘记定义基类析构函数virtual。这意味着在Base *上调用delete不会最终破坏派生部分。

答案 17 :(得分:2)

使用智能指针和容器类时要小心。

答案 18 :(得分:1)

要搞砸了,请使用直指针。相反,几乎任何东西都使用RAII,当然要确保使用正确的智能指针。如果你在句柄或指针类型类之外的任何地方写“删除”,你很可能做错了。

答案 19 :(得分:1)

  
      
  • static_cast向下转播虚拟基类
  •   

不是......现在关于我的误解:我认为下面的A是一个虚拟的基类,而事实上并非如此;根据10.3.1,它是一个多态类。在这里使用static_cast似乎没问题。

struct B { virtual ~B() {} };

struct D : B { };

总之,是的,这是一个危险的陷阱。

答案 20 :(得分:1)

  • Blizpasta。这是一个我看得很多的巨大的......

  • 未初始化的变量是我学生犯下的一个巨大错误。许多Java人忘记了只是说“int counter”没有将counter设置为0.因为你必须在h文件中定义变量(并在对象的构造函数/设置中初始化它们),所以很容易忘记。

  • for循环/数组访问时出现一个错误。

  • 在伏都教启动时未正确清理目标代码。

答案 21 :(得分:1)

保持名称空间直接(包括struct,class,namespace和using)。当程序没有编译时,这是我的头号挫折。

答案 22 :(得分:1)

答案 23 :(得分:0)

意图是(x == 10)

if (x = 10) {
    //Do something
}

我以为自己永远不会犯这个错误,但我最近真的这么做了。

答案 24 :(得分:0)

在取消引用之前,请务必检查指针。在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
}

答案 25 :(得分:0)

论文/文章 Pointers, references and Values 非常有用。它谈论避免避免陷阱和良好做法。您也可以浏览整个站点,其中包含主要针对C ++的编程技巧。

答案 26 :(得分:0)

我花了很多年时间做C ++开发。几年前我写了quick summary个问题。符合标准的编译器不再是一个问题,但我怀疑所概述的其他陷阱仍然有效。

答案 27 :(得分:0)

忘记&,从而创建副本而不是引用。

这种情况在我身上以不同的方式发生过两次:

  • 一个实例位于参数列表中,导致一个大对象被放入堆栈,导致堆栈溢出和嵌入式系统崩溃。

  • 我在实例变量上忘记了&,其效果是复制了对象。在注册为副本后,我想知道为什么我从来没有从原始对象获得回调。

两者都很难被发现,因为差异很小而且难以看到,否则对象和引用在语法上以相同的方式使用。

答案 28 :(得分:-1)

#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);
}