Как сделать так, чтобы при написании самого кода на C ++ он не вызывал утечек памяти? - PullRequest
6 голосов
/ 20 августа 2009

Выполнение valgrind или очистка будет следующим шагом Но во время написания самого кода как вы гарантируете, что он не вызовет утечек памяти? Вы можете обеспечить следующие вещи: - 1: количество новых равно удаляемому 2: Дескриптор открытого файла закрыт или нет

Есть что-нибудь еще?

Ответы [ 13 ]

25 голосов
/ 20 августа 2009

Используйте идиому RAII везде, где вы можете

Используйте умные указатели, например, std :: auto_ptr, где это уместно. (не используйте auto_prt ни в одной из стандартных коллекций, поскольку он не будет работать так, как вы думаете)

14 голосов
/ 20 августа 2009

Избегайте создания объектов динамически, где это возможно. Программисты из Java и других похожих языков часто пишут такие вещи, как:

string * s = new string( "hello world" );

когда им следовало написать:

string s = "hello world";

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

class Person {
   public:
      Person( const string & name ) : mName( name ) {}
      ...
   private:
      string mName;
};

Вместо того, чтобы писать код вроде:

vector <Person *> vp;

или даже:

vector <shared_ptr <Person> > vp;

вместо этого используйте значения:

vector <Person> vp;

Вы можете легко добавить к такому вектору:

vp.push_back( Person( "neil butterworth" ) );

и вся память для Персона и вектор управляется для вас. Конечно, если вам нужна коллекция полиморфных типов, вы должны использовать (умные) указатели

7 голосов
/ 20 августа 2009
5 голосов
/ 20 августа 2009
  1. Использовать RAII
  2. Скрыть по умолчанию копирование ctors, operator = () в КАЖДОМ КЛАССЕ, если а) твой класс тривиален и использует только нативные типы и ВЫ ЗНАЕТЕ ЭТО ВСЕГДА БУДЕТ ТАК Б) ты явно определите свой собственный

В 1) RAII, идея состоит в том, чтобы удаление происходило автоматически, если вы обнаружите, что думаете: «Я только что позвонил new, мне нужно помнить, чтобы вызывать delete где-нибудь», тогда вы делаете что-то не так. Удаление должно быть либо а) автоматическим, либо б) помещаться в dtor (и какой dtor должен быть очевиден).

Вкл. 2) Скрытие значений по умолчанию. Выявление мошеннических копий по умолчанию и т. Д. Может быть кошмаром, проще всего избежать их, скрыв их. Если у вас есть общий «корневой» объект, от которого все наследует (в любом случае это может пригодиться для отладки / профилирования), то скрывайте здесь значения по умолчанию, тогда, когда что-то пытается присвоить / скопировать наследующий класс, компилятор barfs, потому что ctor и т. Д доступно в базовом классе.

4 голосов
/ 20 августа 2009

Минимизируйте вызовы новых, используя контейнеры STL для хранения ваших данных.

3 голосов
/ 20 августа 2009

Я с Гленом и Джалфом по поводу RAII при каждой возможности.

ИМХО, вы должны стремиться написать полностью бездетный код. Единственные явные «delete» должны быть в ваших реализациях класса умного указателя. Если вы захотите написать «удалить», перейдите и найдите подходящий тип интеллектуального указателя. Если ни один из «отраслевых стандартов» (надстройки и т. Д.) Не подходит, и вы хотите написать какой-то странный новый, скорее всего, ваша архитектура сломана или, по крайней мере, в будущем возникнут трудности с обслуживанием.

Я долго считал, что явное «удаление» относится к управлению памятью, а «переход» - к управлению потоком. Подробнее об этом в этом ответе .

1 голос
/ 20 августа 2009

Два простых правила:

  • Никогда не вызывайте delete явно (то есть вне класса RAII). За каждое выделение памяти должен отвечать класс RAII , который вызывает delete в деструкторе.
  • Почти никогда не вызывайте new явно. Если вы это сделаете, вы должны немедленно обернуть результирующий указатель в интеллектуальный указатель, который становится владельцем распределения и работает, как указано выше.

В ваших собственных классах RAII есть две распространенные ошибки:

  • Неправильная обработка копирования: кто становится владельцем памяти, если объект копируется? Они создают новое распределение? Вы реализуете как конструктор копирования и оператор присваивания? Работает ли последний с самостоятельным назначением?
  • Невыполнение требования об исключительной безопасности. Что произойдет, если во время операции возникнет исключение (например, назначение)? Объект возвращается в согласованное состояние? (он должен всегда делать это, несмотря ни на что) Откатывается ли он до состояния, которое было до операции? (он должен делать это, когда это возможно) std::vector должен справиться с этим, например, во время push_back. Это может привести к изменению размера вектора, что означает 1) выделение памяти, которое может быть выброшено, и 2) все существующие элементы должны быть скопированы, каждый из которых может быть сброшен. Алгоритм, подобный std::sort, должен иметь с ним дело. Он должен вызывать предоставленный пользователем компаратор, который потенциально может выдать тоже! если это произойдет, последовательность остается в допустимом состоянии? Разрушены ли временные объекты чисто?

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

1 голос
/ 20 августа 2009

Основные шаги в два раза:

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

Во-вторых, убедитесь, что вы никогда не перезаписываете указатель. Вы можете сделать это, используя класс умных указателей вместо необработанных указателей, но если вы действительно убедитесь, что никогда не используете его с неявным преобразованием. (пример: используя библиотеку MSXML, я создал умный указатель CCOMPtr для хранения узлов, чтобы получить узел, который вы вызываете методом get_Node, передавая адрес умного указателя - у которого был оператор преобразования, который возвращал базовый тип указателя. это означало, что если интеллектуальный указатель уже содержал данные, эти данные-члены будут перезаписаны, что приведет к утечке из предыдущего узла).

Я думаю, что в этих двух случаях вы можете потерять память. Если вы используете только интеллектуальный указатель напрямую - никогда не позволяя раскрыть его внутренние данные, вы защищены от последней проблемы. Если вы закроете весь свой код, который использует new и delete, в классе (то есть, используя RAII), то вы также будете в безопасности от первого.

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

1 голос
/ 20 августа 2009

Я всегда использую std::auto_ptr, когда мне нужно создать новый объект в куче.

std::auto_ptr<Foo> CreateFoo()
{
   return std::auto_ptr<Foo>(new Foo());
}

Даже если вы позвоните

CreateFoo()

не протечет

0 голосов
/ 20 августа 2009

Включите модульное и системное тестирование Valgrind на ранних этапах цикла разработки и используйте его постоянно.

...