Подтвердить что ты не робот

Злые образцы тонко сломанного кода на С++

Мне нужны некоторые примеры плохого кода на С++, которые иллюстрируют нарушение хороших практик. Я хотел придумать свои собственные примеры, но мне трудно придумать примеры, которые не надуманны, и где ловушка не сразу очевидна (это сложнее, чем кажется).

Примерами могут быть:

  • Не задавать конструктор копирования для классов с членами std::auto_ptr и использовать std::auto_ptr членов с классами, объявленными вперед.
  • Вызов виртуальных функций от конструктора или деструктора (прямо или косвенно).
  • Перегрузка функции шаблона.
  • Циркулярные ссылки с boost::shared_ptr.
  • нарезка.
  • Выбрасывание исключений из C обратных вызовов (прямо или косвенно).
  • Сравнение с плавающей точкой для равенства.
  • Исключение безопасности конструкторов с необработанными указателями.
  • Бросание из деструкторов.
  • Целочисленное переполнение при компиляции на разных архитектурах (несоответствие size_t и int).
  • Недействительный итератор контейнера.

... или любое другое зло, о котором вы можете думать.

Я бы оценил некоторые указатели на существующие ресурсы или образец или два.

4b9b3361

Ответ 1

Самый неприятный синтаксический анализ - это удивительно противоречивый результат того, как С++ разбирает такие вещи:

// Declares a function called "myVector" that returns a std::vector<float>.
std::vector<float> myVector(); 
// Does NOT declare an instance of std::vector<float> called "myVector"

// Declares a function called "foo" that returns a Foo and accepts an unnamed
// parameter of type Bar.
Foo foo(Bar()); 
// Does NOT create an instance of Foo called "foo" nor creates a Bar temporary

// Declares a function called "myVector" that takes two parameters, the first named
// "str" and the second unnamed, both of type std::istream_iterator<int>.
std::vector<float> myVector( 
    std::istream_iterator<int>(str),
    std::istream_iterator<int>()
);
// Does NOT create an instance of `std::vector<float>` named "myVector" while copying
// in elements from a range of iterators

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

Ответ 2

#include <iostream>

class Base
{
    public:
        virtual void foo() const { std::cout << "A foo!" << std::endl; }
};

class Derived : public Base
{
    public:
        void foo() { std::cout << "B foo!" << std::endl; }
};

int main()
{
    Base* o1 = new Base();
    Base* o2 = new Derived();
    Derived* o3 = new Derived();

    o1->foo();
    o2->foo();
    o3->foo();
}

И результат:

A foo!
A foo!
B foo!

Не уверен, есть ли у него имя, но это точно зло!: P

Ответ 3

Код, который не является безопасным для исключений, может потерпеть неудачу способами, которые не очевидны для читателей кода:

// Order of invocation is undefined in this context according to the C++ standard.
// It possible to leak a Foo or a Bar depending on the order of evaluation if one
// of the new statements throws an exception before their auto_ptrs can "own" it
accept_two_ptrs(std::auto_ptr<Foo>(new Foo), std::auto_ptr<Bar>(new Bar));

void MyClass::InvokeCallback(CallbackType cb)
{
    Foo* resource = new Foo;
    cb(resource); // If cb throws an exception, resource leaks
    delete resource;
}

Ответ 4

Этот был выше сегодня вечером. Поскольку @Billy ONeal указал на этот пост, цикл на входном потоке, проверяющий только на eof(), может привести к бесконечному циклу, если ошибка возникает в потоке, good() следует использовать вместо этого.

ПЛОХО:

while( !cin.eof() ) {
   getline(cin, input);
}

OK

while( cin.good() ) {
   getline(cin, input);
}

[кредит: @Джеймс Макнеллис]

ОТЛИЧНОЕ:

while (std::getline(std::cin, input)) {
}

Ответ 6

Как вы думаете, что программа будет печатать?

#include <iostream>
using namespace std;

struct A {
    void f(int) { cout << "a" << endl; }
};

struct B: public A {
    void f(bool) { cout << "b" << endl; }
};

int main() {
    B b;
    b.f(true);
    b.f(1);
    A* a = &b;
    a->f(true);
    return 0;
}

Ответ: b, b, a! Первая распечатка очевидна. Второй - b, потому что определение B::f(bool) скрывает определение A::f(int). Третий - a, потому что разрешение перегрузки происходит на статическом типе.

(источник: Гуру недели, но я не могу найти статью.)

Ответ 7

Аргумент-зависимый поиск (ADL, также называемый поиском Koenig) не совсем понятен большинству программистов на С++ и может вызвать некоторые очень необычные результаты, особенно в сочетании с шаблонами.

Я обсуждал одну крупную ловушку ADL в ответ на Каковы подводные камни ADL?


В перегрузке много сложностей. Часто возникают проблемы при использовании директив в области пространства имен, особенно using namespace std, поскольку это пространство имен имеет большое количество объектов с общими именами.

Вот еще два последних примера проблемы using namespace std, вызывающие проблемы:

Ответ 8

Этот, IMHO, тоже сложный:

class Base {
int _value;

public:
    Base() {
        _value = g();
    }

    virtual int f() = 0;

    int g() { return f(); }
};

class Derived: Base {   
public:
    Derived(): Base()
    { /* init Derived */ }

    int f() { /* implementation */ }
}

ваш код выйдет из строя, потому что чистый виртуальный метод f() не реализован. Очевидная причина заключается в том, что Derived еще не завершен в конструкторе, поэтому вы в конечном итоге вызываете виртуальную чистую f() и не будете обнаружены компилятором (обычно компилятор жалуется, если в конструкторе вызывается чистый виртуальный объект).

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