Является ли Foo * f = новый Foo хорошим C ++ кодом - PullRequest
16 голосов
/ 11 января 2010

Читая мой старый журнал C ++, я кое-что заметил.

Одна из статей утверждала, что

Foo *f = new Foo();

был почти неприемлемым профессиональным кодом C ++ в целом, и подходило решение для автоматического управления памятью.

Это так?

edit: rephrased: прямое управление памятью неприемлемо для нового кода C ++, в general ? Следует ли использовать auto_ptr (или другие оболочки управления) для большинства нового кода?

Ответы [ 10 ]

20 голосов
/ 11 января 2010

Этот пример очень похож на Java.
В C ++ мы используем динамическое управление памятью, только если это необходимо.
Лучшая альтернатива - просто объявить локальную переменную.

{
    Foo    f;

    // use f

} // f goes out of scope and is immediately destroyed here.

Если вам необходимо использовать динамическую память, используйте умный указатель.

// In C++14
{
    std::unique_ptr<Foo>  f = std::make_unique<Foo>(); // no need for new anymore
}

// In C++11
{
    std::unique_ptr<Foo>  f(new Foo);  // See Description below.
}

// In C++03
{
    std::auto_ptr<Foo>    f(new Foo);  // the smart pointer f owns the pointer.
                                       // At some point f may give up ownership to another
                                       // object. If not then f will automatically delete
                                       // the pointer when it goes out of scope..

}

Существует целая куча умных указателей, предоставляемых int std :: и boost :: (теперь некоторые из них в std :: tr1), выбирают подходящий и используют его для управления временем жизни вашего объекта.

См. Умные указатели: Или кому принадлежит ваш ребенок?

Технически вы можете использовать new / delete для управления памятью.
Но в реальном C ++ коде это почти никогда не делается. Почти всегда есть лучшая альтернатива ручному управлению памятью.

Простым примером является std :: vector. Под обложками он использует новые и удаляет. Но вы никогда не сможете сказать извне. Это полностью прозрачно для пользователя класса. Все, что знает пользователь, - это то, что вектор станет владельцем объекта, и он будет уничтожен при уничтожении вектора.

8 голосов
/ 11 января 2010

Я думаю, проблема всех этих «... лучших практик ...» в том, что они все рассматривают код без контекста. Если вы спросите «в общем», я должен признать, что прямое управление памятью вполне приемлемо. Это синтаксически допустимо и не нарушает семантику языка.

Что касается альтернатив (переменные стека, интеллектуальные указатели и т. Д.), У них всех есть свои недостатки. И ни у одного из них нет такой гибкости, как у прямого управления памятью. Цена, которую вы должны заплатить за такую ​​гибкость, - это время отладки, и вы должны знать обо всех рисках.

7 голосов
/ 11 января 2010

номер

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

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

Какой-то умный класс указателей, такой как boost :: shared_ptr, boost :: scoped_ptr, будет хорошим началом. (Они будут частью стандарта C ++ 0x, поэтому не бойтесь их;))

6 голосов
/ 11 января 2010

Если вы используете исключения, такой код практически гарантированно приведет к утечке ресурсов. Даже если вы отключите исключения, очистка будет очень проста при ручном сопряжении нового с delete.

6 голосов
/ 11 января 2010

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

4 голосов
/ 11 января 2010

Это зависит от того, что именно мы имеем в виду.

  • Должно ли new никогда не использоваться для выделения памяти? Конечно, у нас нет другого выхода. new - это способ для динамического размещения объектов в C ++. Когда нам нужно динамически выделить объект типа T, мы делаем new T(...).
  • Должен ли new вызываться по умолчанию , когда мы хотим создать новый объект? NO . В java или C # new используется для создания новых объектов, поэтому вы используете его везде. в C ++ он используется только для выделения кучи. Почти все объекты должны быть размещены в стеке (или созданы на месте как члены класса), чтобы правила видимости языка помогли нам управлять их временем жизни. new не часто необходимо. Обычно, когда мы хотим разместить новые объекты в куче, вы делаете это как часть большой коллекции, и в этом случае вам просто нужно поместить объект в свой контейнер STL и позволить ему беспокоиться о выделении и освобождении памяти. Если вам нужен только один объект, его обычно можно создать как член класса или локальную переменную, без использования new.
  • Должен ли new присутствовать в вашем коде бизнес-логики? Редко, если когда-либо. Как упомянуто выше, это может и должно быть обычно скрыто в классах обертки. std::vector например, динамически распределяет необходимую память. Так что пользователь из vector не должен заботиться. Я просто создаю вектор в стеке, и он заботится о распределении кучи для меня. Когда векторный или другой контейнерный класс не подходит, мы можем захотеть написать нашу собственную оболочку RAII, которая выделяет некоторую память в конструкторе с new и освобождает ее в деструкторе. И эта обертка может быть выделена в стеке, поэтому пользователь класса никогда не должен вызывать new.

В одной из статей утверждалось, что Foo *f = new Foo(); был практически неприемлемым профессиональным кодом C ++ в целом, и подходящим было решение для автоматического управления памятью.

Если они имеют в виду то, что я думаю, они имеют в виду, тогда они правы. Как я уже говорил выше, new обычно следует скрывать в классах-обёртках, где об этом может позаботиться автоматическое управление памятью (в форме времени жизни с областью действия и объектов, вызывающих свои деструкторы, когда они выходят из области видимости). В статье не говорится «никогда ничего не выделяйте в куче» или никогда не используйте new », а просто« Когда вы используете new, не просто сохраняйте указатель на выделенную память. Поместите его в какой-то класс, который может позаботиться о том, чтобы выпустить его, когда он выйдет из области видимости.

Вместо Foo *f = new Foo(); вы должны использовать один из них:

Scoped_Foo f; // just create a wrapper which *internally* allocates what it needs on the heap and frees it when it goes out of scope
shared_ptr<Foo> f = new Foo(); // if you *do* need to dynamically allocate an object, place the resulting pointer inside a smart pointer of some sort. Depending on circumstances, scoped_ptr, or auto_ptr may be preferable. Or in C++0x, unique_ptr
std::vector<Foo> v; v.push_back(Foo()); // place the object in a vector or another container, and let that worry about memory allocations.
2 голосов
/ 11 января 2010

Я перестал писать такой код некоторое время назад. Есть несколько альтернатив:

Удаление на основе области

{
    Foo foo;
    // done with foo, release
}

scoped_ptr для динамического выделения на основе области действия

{
    scoped_ptr<Foo> foo( new Foo() );
    // done with foo, release
}

shared_ptr для вещей, которые должны обрабатываться во многих местах

shared_ptr<Foo> foo;
{ 
    foo.reset( new Foo() );
} 
// still alive
shared_ptr<Foo> bar = foo; // pointer copy
...
foo.reset(); // Foo still lives via bar
bar.reset(); // released

Управление ресурсами на основе оборудования

Foo* foo = fooFactory.build();
...
fooFactory.release( foo ); // or it will be 
                           // automatically released 
                           // on factory destruction
1 голос
/ 11 января 2010

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

Из ответа я написал на другой вопрос:

Работа программиста заключается в выражении вещи элегантно на его языке выбор.

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

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

Хотя, возможно, не один родитель. Следующая лучшая вещь - это последовательность приемные родители. Это то, что auto_ptr для. Все еще довольно хорошо, потому что программист должен знать какой конкретный родитель является владельцем. Время жизни объекта ограничено время жизни его последовательности владельцы. Один шаг вниз по цепочке в детерминизм и сама по себе элегантность shared_ptr: время жизни ограничено союз пула владельцев.

> Но, возможно, этот ресурс не одновременно с любым другим объектом, установите объектов, или поток управления в система. Он создан на каком-то событии происходит и разрушается на другом событие. Хотя есть много инструменты для определения продолжительности жизни делегации и другие жизни, они не достаточно для вычисления любого произвольная функция. Итак, программист может решить написать функцию несколько переменных, чтобы определить, объект появляется или исчезают, и звоните new и delete.

Наконец, написание функций может быть жесткий. Может быть, правила, регулирующие объект займет слишком много времени и память на самом деле вычислить! И это может быть просто очень трудно выразить их элегантно, возвращаясь к моему исходная точка. Так что для этого у нас есть сборка мусора: объект время жизни ограничено тем, когда вы хотите это, а когда нет.

0 голосов
/ 11 января 2010

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

0 голосов
/ 11 января 2010

Прежде всего, я считаю, что это должно быть Foo *f = new Foo();

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...