您从经验中学到了哪些与C ++相关的习语,误解和陷阱?

时间:2008-11-16 15:47:35

标签: c++ idioms

您从经验中学到了哪些与C ++相关的习语,误解和陷阱?

一个例子:

class A
{
  public: 
  char s[1024];
  char *p;

  A::A()
  {
    p = s;
  }

  void changeS() const
  {
    p[0] = 'a';
  }

};

即使知道changeS是一个const成员函数,它也在改变对象的值。因此const成员函数只意味着它将所有变量视为const,并不意味着它实际上将所有成员保持为const。 (为什么?成员函数上的const关键字将char * p视为char * const p;而不是const char * p;

因此,这意味着p不能指向别的东西。而不是你不能改变p的数据。

16 个答案:

答案 0 :(得分:55)

您不需要了解C ++的复杂函数typedef声明语法。这是我发现的一个可爱的伎俩。

快速,描述一下这个typedef:

typedef C &(__cdecl C::* const CB )(const C &) const;

轻松! CB是指向C类成员函数的指针,它接受对C对象的const引用并返回对C对象的非const引用。哦,这是一个const成员函数。哦,函数指针本身就是const ...(对吧?)

C ++函数声明规范语法众所周知地是钝的,难以记住。是的,经验丰富的C ++老手可能会使用这些技巧来破译这样的恐怖事件,但这不是这个提示的内容。这个提示是关于你如何不需要记住这个可怕的语法,并且仍然能够声明这样的函数指针typedef(例如,如果你正在与一些从未听说过boost :: function的遗留API进行交互)。 让编译器为您完成工作,而不是打破精神上的汗水。下次您尝试为成员函数创建一个typedef,如下所示:

struct C {
        const C& Callback(const C&) const   { }
};

不要努力手动提出上面的复杂语法,而是引发有意的编译错误,这将迫使编译器命名为野兽。

例如:

char c = &C::Callback;

编译器很乐意发出这条有用的错误消息:

“… cannot convert from 'const C &(__cdecl C::* )(const C &) const' to 'char'”

这是我们正在寻找的。 :)

答案 1 :(得分:18)

自从我在某些代码中发现它以来,我就喜欢这个:

assert(condition || !"Something has gone wrong!");

或者如果您手边没有条件,您可以这样做

assert(!"Something has gone wrong!");

以下内容归属于@Josh(见评论)。它使用comma operator代替:

assert(("Something has gone wrong!", condition)); 

答案 2 :(得分:18)

这是我有一天抓到的另一个:

char int2hex(int x) {
     return "-0123456789abcdef"[(x >= 0 && x < 16) ? (x + 1) : 0];
}

它只是索引char数组而不是进行切换。如果它超出范围,则返回' - '。

答案 3 :(得分:14)

当我们不知道以后是否需要它时,不要浪费时间尝试在类上实现复制操作。我们处理的许多对象只是实体,复制它们几乎没有任何意义。使它们不可复制,如果确实需要,可以在以后实施复制/复制。

答案 4 :(得分:11)

有时,标题会被污染,但不会出现像

这样的宏名称
#define max(a, b) (a > b ? a : b)

使用以这种方式调用的max函数或函数对象将使代码无效。一个臭名昭着的例子是windows.h,它就是这样做的。绕过它的一种方法是在调用周围加上括号,这会阻止它使用宏并使其使用真正的最大函数:

void myfunction() {
    ....
    (max)(c, d);
}

现在,最大值在括号中,不再算作对宏的调用了!

答案 5 :(得分:7)

很少使用,但方便的C ++习惯用法是在构造函数链中使用?:运算符。

class Sample
{  
    const char * ptr;
    const bool  freeable;

    Sample(const char * optional):
        ptr( optional ? optional : new char [32]),
        freeable( optional ? false : true ) {}
    ~Sample( )  { if (freeable) delete[] ptr; }
}  

C ++不允许在构造函数体内更改const值,因此这样可以避免使用const-cast。

答案 6 :(得分:6)

您经常可以在源文件中隐藏比您想象的更多内容。如果不需要,请不要将所有内容设为私有 - 通常最好将其保留在源文件中的匿名命名空间中。事实上,它实际上使事情变得更容易处理,因为那时你并没有透露实现细节,而是获得灵感来制作许多小函数而不是单片函数。

答案 7 :(得分:5)

通常会让人吵架的一些事情:

std::cout << a << a++ << --a;
i = ++i;

以上几行均未定义。

void foo(bar* b1, bar* b2);

int main() {
  foo(shared_ptr<bar>(new bar()), shared_ptr<bar>(new bar()));
}

以上情况可能会泄漏记忆。

int* arr = new int[10];
arr + 11;

这会导致未定义的行为。

至于成语,我最喜欢的是RAII。在堆栈上分配对象,这可以保证在对象超出范围时调用析构函数,从而防止资源泄漏。

答案 8 :(得分:5)

因为我们都忽略了OP,而是发布了我们最喜欢的酷炫技巧......

使用boost(或tr1)shared_ptr在运行时维护类不变量(显而易见,但我还没有看到其他人这样做):

#include <cassert>
#include <functional>
#include <stdexcept>
#include <boost/shared_ptr.hpp>
using namespace std;
using namespace boost;

class Foo
{
public:
    Foo() : even(0)
    {
        // Check on start up...
        Invariant();
    }

    void BrokenFunc()
    {
        // ...and on exit from public non-const member functions.
        // Any more is wasteful.
        shared_ptr<Foo> checker(this, mem_fun(&Foo::Invariant));

        even += 1;
        throw runtime_error("didn't expect this!");
        even += 1;
    }

private:
    void Invariant() { assert(even % 2 == 0); }
    int even;
};

答案 9 :(得分:4)

由于我了解了RAII(有史以来最糟糕的缩写词之一)和智能指针,因此内存和资源泄漏几乎完全消失。

答案 10 :(得分:3)

如果您有一个没有值语义的类,请确保声明显式所有以下结构,以防止出现令人头疼的问题。

  • 默认构造函数
  • 复制构造函数
  • 作业运营商

在许多情况下,您只需要声明这些构造的子集。然而,在某些情况下,它可能变得非常棘手,哪些是需要的,哪些不是。声明所有3个私有并且完成此事更安全。

向顶部添加注释以解释这不是复制安全类也非常有用。

为您节省时间。

答案 11 :(得分:0)

我不能说我是一位经验丰富的C ++程序员,但我最近才知道将数组数组作为函数参数传递是多么困难。尽量避免这种情况:(

如果你知道编译时的大小那么简单。即使您在编译时知道其中一个维度。 如果你根本就不知道......你可能会看到类似这样的东西

m[i*dim2+j]

作为行的迭代器,dim2 cols的数量,j是cols的迭代器。

答案 12 :(得分:0)

编程时我没有遇到任何问题,但是朋友想要解释代码工作的原因。我花了一段时间才搞清楚。也许这对你们很明显,但我不是一位经验丰富的程序员。

#include <iostream>
using namespace std;

int& strangeFunction(int& x){return x;}


int main(){
        int a=0;
        strangeFunction(a) = 5;               //<------- I found this very confusing
        cout << a <<endl;
        return 0;
}

答案 13 :(得分:0)

除非需要,否则不要使用shared_ptr。更喜欢使用C ++引用和unique_ptrshared_ptr是一种性能损失,使代码看起来比它复杂。通常情况下,这不会是一个问题,除了shared_ptr的不寻常的吸引力及其传染性。

答案 14 :(得分:0)

除非需要,否则不要陷入使用std::noncopyable的陷阱。是的,它在很多地方很有用,应该在那里使用。

陷阱是一个人开始编写clone()函数并使其不可复制,这实现了相同的功能。相反,您也可以使用explicitlink)来复制构造函数,以防止意外复制(并使赋值变为私有,或删除C ++ 0x中的函数)。但是,对于继承的基类,需要clone()

答案 15 :(得分:0)

使用boost::spirit::hold_anylink)代替boost::any获取性能代码(同时保存大量小对象)。我看到他们的表现有很大差异。