Какие идиомы, заблуждения и ошибки, связанные с C ++, вы узнали из опыта? - PullRequest
27 голосов
/ 16 ноября 2008

Какие некоторые идиомы, заблуждения и ошибки, связанные с C ++, вы узнали из опыта?

Пример:

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

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

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

};

Даже знаю, что changeS - это постоянная функция-член, она меняет значение объекта. Таким образом, функция-член const означает только то, что она будет обрабатывать все переменные как const, и это не значит, что она на самом деле будет поддерживать все члены const. (почему? Ключевое слово const в функции-члене рассматривает char * p; как char * const p; а не как const char * p;

Что означает, что p не может указывать на что-то еще. И не то, что вы не можете изменить данные р.

Ответы [ 16 ]

55 голосов
/ 16 ноября 2008

Вам не нужно знать синтаксис объявления сложной функции typedef в C ++. Вот милый трюк, который я нашел.

Быстро, опишите этот typedef:

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

Легко! CB - указатель на функцию-член класса C, принимающую константную ссылку на объект C и возвращающую неконстантную ссылку на объект C. О, и это постоянная функция-член. О, и сам указатель на функцию является константой ... (верно?)

Синтаксис спецификации объявления функции C ++ общеизвестно тупой и его трудно запомнить. Да, есть уловки опытных ветеранов C ++, которые могут использовать для расшифровки таких ужасов, но это не то, о чем этот совет. Этот совет о том, как вам не нужно запоминать этот ужасный синтаксис и при этом иметь возможность объявлять такие определения типов указателей на функции (например, если вы взаимодействуете с каким-то устаревшим API, который никогда не слышал о boost :: function). Вместо того, чтобы ломать голову, пусть компилятор сделает всю работу за вас. В следующий раз вы пытаетесь создать 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'”

Что мы и ищем. :)

18 голосов
/ 16 ноября 2008

Вот еще один, который я поймал однажды:

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

это просто индексирование массива символов вместо переключения. Если он находится за пределами диапазона, возвращается «-».

18 голосов
/ 16 ноября 2008

Мне понравилось это с того времени, как я обнаружил это в каком-то коде:

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

или если у вас нет условия, вы можете просто сделать

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

Следующее относится к @ Josh (см. Комментарии). Вместо него используется оператор запятой :

assert(("Something has gone wrong!", condition)); 
14 голосов
/ 16 ноября 2008

Никогда не тратьте время на попытки реализовать операции копирования в классах, когда мы не знаем, потребуется ли это позже. Многие объекты, с которыми мы работаем, являются просто сущностями, и копирование их вряд ли имеет смысл. Сделайте их не копируемыми, а потом скопируйте / дублируйте их, если в этом действительно возникнет необходимость.

11 голосов
/ 10 марта 2009

Иногда заголовки загрязняются не ведущими именами макросов, такими как

#define max(a, b) (a > b ? a : b)

Который сделает код недопустимым, использующим функцию max или объект функции, названный таким образом. Печально известный пример - windows.h, который делает именно это. Один из способов обойти это - поставить скобки вокруг вызова, что останавливает его от использования макроса и заставляет использовать реальную функцию max:

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

Теперь максимальное значение указано в скобках, и оно больше не считается вызовом макроса!

7 голосов
/ 16 ноября 2008

Одна редко используемая, но удобная идиома 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 ++ не позволяет изменять значения констант внутри тела конструктора, поэтому это позволяет избежать приведения констант.

6 голосов
/ 17 ноября 2008

В исходных файлах часто можно спрятать гораздо больше, чем вы думаете. Не делайте все частным, если вам не нужно - часто лучше оставить это в анонимном пространстве имен в исходном файле. Я считаю, что на самом деле это облегчает обработку, потому что тогда вы не раскрываете подробности реализации, а получаете вдохновение для создания множества мелких функций, а не монолитных.

5 голосов
/ 16 ноября 2008

Так как мы все игнорируем ОП и публикуем наши любимые классные трюки ...

Используйте 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;
};
5 голосов
/ 16 ноября 2008

Несколько вещей, которые обычно путают людей:

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 . Выделите объекты в стеке, что гарантирует, что деструктор вызывается, когда объект выходит из области видимости, предотвращая утечку ресурсов.

4 голосов
/ 17 ноября 2008

С тех пор, как я узнал о RAII (одной из худших аббревиатур) и умных указателях, утечки памяти и ресурсов почти полностью исчезли.

...