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

Какие недостатки С++ следует избегать?

Я помню, как сначала узнал о векторах в STL, и через некоторое время я хотел использовать вектор bools для одного из моих проектов. Увидев какое-то странное поведение и сделав некоторые исследования, я узнал, что вектор bools на самом деле не является вектором bools.

Есть ли в С++ другие распространенные ошибки?

4b9b3361

Ответ 1

Краткий список может быть:

  • Избегайте утечек памяти с помощью общих указателей для управления распределением и очисткой памяти.
  • Используйте Инициация приобретения ресурсов (RAII) для управления очисткой ресурсов - особенно при наличии исключений
  • Избегайте вызывать виртуальные функции в конструкторах
  • При необходимости используйте минималистские методы кодирования - например, объявляя переменные только тогда, когда это необходимо, переменные области видимости и, если это возможно, разрабатывайте проект.
  • Поистине понимаем обработку исключений в вашем коде - как в отношении исключений, которые вы бросаете, так и для брошенных вами классов, которые вы можете использовать косвенно. Это особенно важно при наличии шаблонов.

RAII, общие указатели и минималистское кодирование, конечно, не являются специфическими для С++, но они помогают избежать проблем, которые часто возникают при разработке на языке.

Некоторые отличные книги по этому вопросу:

  • Эффективный С++ - Скотт Мейерс
  • Более эффективный С++ - Скотт Майерс
  • Стандарты кодирования С++ - Sutter и Alexandrescu
  • Часто задаваемые вопросы по С++ - Cline

Чтение этих книг помогло мне больше всего, чтобы избежать подобных ошибок, о которых вы просите.

Ответ 2

Ловушки в порядке убывания их важности

Прежде всего, вы должны посетить награжденный С++ FAQ. У него много хороших ответов на ловушки. Если у вас есть дополнительные вопросы, посетите ##c++ on irc.freenode.org в IRC. Мы будем рады помочь вам, если сможем. Обратите внимание, что все следующие ловушки изначально написаны. Они не просто скопированы из случайных источников.


delete[] на new, delete на new[]

Решение. Выполнение приведенного выше результата приводит к undefined: все может случиться. Поймите свой код и что он делает, и всегда delete[] что вы new[], и delete что вы new, тогда этого не произойдет.

Exception

typedef T type[N]; T * pT = new type; delete[] pT;

Вам нужно delete[], хотя вы new, так как вы новичок в массиве. Поэтому, если вы работаете с typedef, обратите особое внимание.


Вызов виртуальной функции в конструкторе или деструкторе

Решение. Вызов виртуальной функции не будет вызывать функции переопределения в производных классах. Вызов чистой виртуальной функции в конструкторе или дескрипторе - это поведение undefined.


Вызов delete или delete[] на уже удаленном указателе

Решение: присвойте 0 каждому удаляемому указателю. Вызов delete или delete[] на нулевом указателе ничего не делает.


Взятие sizeof указателя, когда нужно вычислить количество элементов "массива".

Решение. Пропустите количество элементов рядом с указателем, когда вам нужно передать массив как указатель на функцию. Используйте предложенную функцию здесь, если вы берете sizeof массива, который должен быть действительно массивом.


Использование массива, как если бы это был указатель. Таким образом, используя T ** для двух размерных массивов.

Решение: см. здесь, почему они разные и как вы их обрабатываете.


Запись в строковый литерал: char * c = "hello"; *c = 'B';

Решение. Выделите массив, который инициализирован из данных строкового литерала, затем вы можете написать ему:

char c[] = "hello"; *c = 'B';

Запись в строковый литерал - это поведение undefined. В любом случае приведенное выше преобразование из строкового литерала в char * устарело. Поэтому компиляторы, вероятно, предупреждают, если вы повышаете уровень предупреждения.


Создание ресурсов, а затем забывание освобождать их, когда что-то бросает.

Решение. Используйте интеллектуальные указатели, такие как std::unique_ptr или std::shared_ptr, как указано в других ответах.


Изменение объекта дважды, как в этом примере: i = ++i;

Решение. Вышеупомянутое должно было присваивать i значение i+1. Но то, что он делает, не определено. Вместо того, чтобы увеличивать i и присваивая результат, он также меняет i на правую сторону. Изменение объекта между двумя точками последовательности - это поведение undefined. Точки последовательности включают ||, &&, comma-operator, semicolon и entering a function (неисчерпывающий список!). Измените код следующим образом, чтобы он корректно вел себя: i = i + 1;


Разное Проблемы

Забытие для сброса потоков перед вызовом функции блокировки, например sleep.

Решение. Пропустите поток путем потоковой передачи std::endl вместо \n или путем вызова stream.flush();.


Объявление функции вместо переменной.

Решение: проблема возникает из-за того, что компилятор интерпретирует, например,

Type t(other_type(value));

как объявление функции функции t, возвращающего Type и имеющего параметр типа other_type, который называется value. Вы разрешаете его, помещая круглые скобки вокруг первого аргумента. Теперь вы получаете переменную t типа Type:

Type t((other_type(value)));

Вызов функции свободного объекта, который объявляется только в текущем блоке перевода (.cpp file).

Решение: Стандарт не определяет порядок создания свободных объектов (в области пространства имен), определенных в разных единицах перевода. Вызов функции-члена для объекта, который еще не сконструирован, - это поведение undefined. Вы можете определить следующую функцию в блоке трансляции объектов и вызвать ее из других:

House & getTheHouse() { static House h; return h; }

Это создаст объект по требованию и оставит вас с полностью сконструированным объектом во время вызова функций на нем.


Определение шаблона в файле .cpp, в то время как оно используется в другом файле .cpp.

Решение. Почти всегда вы получите ошибки, такие как undefined reference to .... Поместите все определения шаблонов в заголовок, так что, когда компилятор их использует, он уже может создать необходимый код.


static_cast<Derived*>(base); Если base является указателем на виртуальный базовый класс Derived.

Решение. Виртуальный базовый класс - это база, которая встречается только один раз, даже если она несколько раз наследуется разными классами косвенно в дереве наследования. Выполнение вышеуказанного не допускается Стандартом. Используйте dynamic_cast для этого и убедитесь, что ваш базовый класс является полиморфным.


dynamic_cast<Derived*>(ptr_to_base); Если база не полиморфна

Решение. Стандарт не позволяет опускать указатель или ссылку, когда переданный объект не является полиморфным. Он или один из его базовых классов должен иметь виртуальную функцию.


Принятие вашей функции T const **

Решение. Вы можете подумать, что безопаснее, чем использование T **, но на самом деле это вызовет головную боль для людей, которые хотят пройти T**: стандарт не позволяет этого. Он дает аккуратный пример того, почему он запрещен:

int main() {
    char const c = ’c’;
    char* pc;
    char const** pcc = &pc; //1: not allowed
    *pcc = &c;
    *pc = ’C’; //2: modifies a const object
}

Всегда принимайте T const* const*;.

Еще одна (закрытая) проблема с С++, поэтому люди, которые ищут их, найдут их, это проблема с переполнением стека С++.

Ответ 4

Брайан имеет отличный список: я бы добавил "Всегда отмечать конструкторы одиночных аргументов явным (за исключением тех редких случаев, которые вы хотите автоматическое кастинг)".

Ответ 5

Не совсем конкретный совет, но общий ориентир: проверьте свои источники. С++ - это старый язык, и за эти годы он сильно изменился. С ним изменились лучшие практики, но, к сожалению, там еще много старой информации. Здесь были очень хорошие рекомендации по книге - я могу купить каждую из книг Скотта Мейерса С++. Ознакомьтесь с Boost и стилями кодирования, используемыми в Boost - люди, участвующие в этом проекте, находятся на переднем крае дизайна С++.

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

Используйте static_cast, dynamic_cast, const_cast и reinterpret_cast вместо C-style cast. В отличие от стилей в стиле C, они сообщают вам, если вы действительно просите о другом типе, чем вы думаете, о чем вы просите. И они выделяются визуально, предупреждая читателя о том, что актерский состав имеет место.

Ответ 6

Веб-страница С++ Pitfalls от Scott Wheeler охватывает некоторые из основных подводных камней С++.

Ответ 7

Я уже упоминал об этом несколько раз, но книги Скотта Мейерса Эффективный С++ и Эффективный STL действительно стоит своего веса в золоте для помощи с С++.

Подумайте об этом, Стивен Dewhurst С++ Gotchas также является отличным ресурсом "из траншей". Его статья о том, чтобы переделать собственные исключения и как они должны быть построены, действительно помогла мне в одном проекте.

Ответ 8

Две ошибки, которые, как мне бы хотелось, я не усвоил:

(1) Множество выходных данных (например, printf) по умолчанию буферизируется. Если вы отлаживаете сбойный код и используете буферизованные операторы отладки, последний вывод, который вы видите, может быть не последним оператором печати, встречающимся в коде. Решение состоит в том, чтобы очистить буфер после каждой отладочной печати (или вообще отключить буферизацию).

(2) Будьте осторожны с инициализацией - (a) избегайте экземпляров экземпляров как глобальные/статические; и (б) попытайтесь инициализировать все переменные-члены для некоторого безопасного значения в ctor, даже если это тривиальное значение, такое как NULL для указателей.

Рассуждение: упорядочение глобальной инициализации объекта не гарантируется (глобальные переменные включают статические переменные), поэтому вы можете в конечном итоге получить код, который, как представляется, не детерминирован, поскольку он зависит от объекта X, который инициализируется перед объектом Y. Если вы не явно инициализировать переменную примитивного типа, такую ​​как член bool или enum класса, в конечном итоге вы получите разные значения в неожиданных ситуациях - опять-таки поведение может показаться очень недетерминированным.

Ответ 9

Использование С++ типа C. Наличие цикла создания и освобождения в коде.

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

Все ресурсы, которые имеют ручное создание и выпуск, должны быть обернуты в объект, чтобы эти действия выполнялись в конструкторе/деструкторе.

// C Code
void myFunc()
{
    Plop*   plop = createMyPlopResource();

    // Use the plop

    releaseMyPlopResource(plop);
}

В С++ это должно быть обернуто в объект:

// 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.
}

Ответ 10

Книга С++ Gotchas может оказаться полезной.

Ответ 11

Вот несколько ям, в которые я имел несчастье попасть. Все это имеет веские причины, которые я понял только после того, как меня укусило поведение, которое меня удивило.

  • virtual функции в конструкторах не являются.

  • Не нарушайте ODR (одно правило определения), что для анонимных пространств имен (среди прочего).

  • Порядок инициализации членов зависит от порядка, в котором они объявлены.

    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`
    

Ответ 12

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

Ответ 13

Проверьте boost.org. Он предоставляет множество дополнительных функций, особенно их интеллектуальные реализации указателей.

Ответ 15

  • Не читайте С++ FAQ Lite. Это объясняет многие плохие (и хорошие!) Практики.
  • Не использовать Boost. Вы сэкономите много разочарований, воспользовавшись Boost, где это возможно.

Ответ 16

Будьте осторожны при использовании интеллектуальных указателей и классов контейнеров.

Ответ 18

Забыть определить виртуальный виртуальный дескриптор базового класса. Это означает, что вызов delete на базе * не приведет к разрушению производной части.

Ответ 20

Держите пространства имен прямо (включая структуру, класс, пространство имен и использование). Это мое разочарование номер один, когда программа просто не компилируется.

Ответ 21

Чтобы испортиться, используйте прямые указатели. Вместо этого используйте RAII практически для всех, убедившись, конечно, что вы используете правильные интеллектуальные указатели. Если вы пишете "удалить" где-либо вне класса дескриптора или типа указателя, вы, скорее всего, ошибаетесь.

Ответ 22

  • Blizpasta. Это огромный, который я вижу много...

  • Неинициализированные переменные - огромная ошибка, которую делают мои ученики. Многие люди из Java забывают, что просто "int counter" не устанавливает счетчик 0. Поскольку вам нужно определить переменные в h файле (и инициализировать их в конструкторе/настройке объекта), его легко забыть.

  • Ошибки по очереди на for доступ к петлям/массиву.

  • Не правильно очищать код объекта при запуске voodoo.

Ответ 23

  • static_cast downcast в виртуальном базовом классе

Не совсем... Теперь о моем заблуждении: я думал, что A в следующем было виртуальным базовым классом, когда на самом деле это не так; это, согласно 10.3.1, полиморфный класс. Использование static_cast здесь кажется прекрасным.

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

struct D : B { };

В общем, да, это опасная ловушка.

Ответ 24

Всегда проверяйте указатель перед его разыменованием. В 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

Намерение (x == 10):

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

Я думал, что никогда не сделаю эту ошибку сам, но я действительно сделал это недавно.

Ответ 26

Эссе/статья Указатели, ссылки и значения очень полезны. В нем говорится об избежании ошибок и передовой практики. Вы также можете просматривать весь сайт, содержащий советы по программированию, в основном для С++.

Ответ 27

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

Ответ 28

Забыть & и тем самым создать копию вместо ссылки.

Это случилось со мной дважды по-разному:

  • Один экземпляр был в списке аргументов, из-за чего большой стек был помещен в стек с результатом и сбоя встроенной системы.

  • Я забыл & в переменной экземпляра с тем, что объект был скопирован. После регистрации в качестве прослушивателя копии я задавался вопросом, почему я никогда не получал обратные вызовы от исходного объекта.

И то и другое, где довольно сложно определить, потому что разница небольшая и трудно различимая, и в противном случае объекты и ссылки используются синтаксически одинаково.

Ответ 29

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