Злые образцы тонко сломанного кода на С++
Мне нужны некоторые примеры плохого кода на С++, которые иллюстрируют нарушение хороших практик. Я хотел придумать свои собственные примеры, но мне трудно придумать примеры, которые не надуманны, и где ловушка не сразу очевидна (это сложнее, чем кажется).
Примерами могут быть:
- Не задавать конструктор копирования для классов с членами
std::auto_ptr
и использовать std::auto_ptr
членов с классами, объявленными вперед.
- Вызов виртуальных функций от конструктора или деструктора (прямо или косвенно).
- Перегрузка функции шаблона.
- Циркулярные ссылки с
boost::shared_ptr
.
- нарезка.
- Выбрасывание исключений из C обратных вызовов (прямо или косвенно).
- Сравнение с плавающей точкой для равенства.
- Исключение безопасности конструкторов с необработанными указателями.
- Бросание из деструкторов.
- Целочисленное переполнение при компиляции на разных архитектурах (несоответствие
size_t
и int
).
- Недействительный итератор контейнера.
... или любое другое зло, о котором вы можете думать.
Я бы оценил некоторые указатели на существующие ресурсы или образец или два.
Ответы
Ответ 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)) {
}
Ответ 5
Перегрузка оператора присваивания, но неправильно обрабатывает самосознание.
Ответ 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()
и не будете обнаружены компилятором (обычно компилятор жалуется, если в конструкторе вызывается чистый виртуальный объект).
В любом случае может случиться так, что виртуальный чистый вызывается, если у вас есть сложный конструктор, который вызывает другую функцию-член, и у вас нет модульных тестов на месте.