Pull to refresh

Comments 44

Почему-то не привели аналог boost:scoped_ptr<> из С++11 — std::unique_ptr<>.
std::unique_ptr<> — это аналог не boost:scoped_ptr<>, а std::auto_ptr<>

Его точно так же можно возвращать из функции (но его также можно помещать в контейнер STL, в отличии от предшественника). А с boost:scoped_ptr<> нельзя ничего этого делать, поскольку он несколько обделен конструкторами.
В С++11 (как и библиотеке Boost) существует ф-ция make_shared, используя которую совместно с ключевым словом auto, проблему boiler-plate можно решить так:
auto ptr = std::make_shared<MyNamespace::Object>(param1, param2, param3);
Открою секрет, использование это функции по двум причинам лучше, чем вызов конструктора.
1) Не нужно 2 раза повторять имя класса ::std::shared_ptr(new T(...)) — короче
2) 1 аллокация памяти вместо двух. В случае ::std::shared_ptr(new T()), сначала вы вызываете аллокацию (new T), а затем shared_ptr для счетчика ссылок. В случае make_shared, вызывается одна аллокацию большого куска, в которую потом размещающим конструктором будет положен ваш объект. Так что этот способ еще и эффективнее.
Парсер съел аргументы шаблона shared_ptr. В певром случае T повторяется 2 раза.
а насколько это удобнее чем ручное выделение и освобождение?
UFO just landed and posted this here
Может быть я какойто мутант с гипертрофированой кратковременной памятью, но у меня никогда не возникало проблем с тем чтоб прибрать за собой, а вот автоматические гарбачколлекторы всегда мне создавали неудобства.
Наверное, вы никогда не писали код с иключениями. Или, если быть более строгим, вы никогда не писали код «безопасный относительно исключений». Там без RAII почти никак, если не впихивать try-catch куда только можно описанным ниже bogolt-ом способом
Обычно после подобной пары фраз от разных людей начинается Holy War.
Представьте себе что вы вызываете функцию и каждый раз должны думать не кидает ли она откуда-то внутри исключение? Если кидает — значит его нужно поймать, и внутри вызывать освобождение ресурсов. Но не всегда можно определить будет ли выброшено исключение или нет. К примеру у вас библиотека, вызывающая пользовательские коллбэки ( например виртуальные фукнции ) и вы в своем коде вообще не имеете представления что реально будет вызвано. Да вы можете каждый раз в таких случаях делать
catch(...)
{
freeMyMemory();
throw;
}

но чем больше лишнего кода — тем выше вероятность ошибиться.

Умные же указатели просто снимают с вас заботу об огромном пласте языка. Причем совсем не обязательно чтобы они следили только за памятью — они могут беречь любой ресурс — будь то файл, сокет или еще какой-то хэндл.

Я уже молчу про то что они могут помочь при многопоточности, и еще раз гарантировать вам что объекты проживут ровно столько сколько они нужны.
Со временем на столько привыкаешь, что без них уже как-то не в кайф :)
в два с половиной раза
Еще забыли упоминуть, не только о том что могут быть выброшены исключения во время работы с выделенными ресурсами, и тогда такой код:
int *myPixels = new int[640 * 480];
// работаем
delete [] pixels;

Так же может генерировать утечки памяти, а вы написали — В принципе, никакой разницы.
Было бы хорошо добавить по unique_ptr и про weak_ptr.
Да, я думал об этом, но статья и так получилась объемной. Попробую в следующей.
Мне кажется, что в вводной части статьи также необходимо хотябы дать ссылку на правило трех, а так — спасибо!
make_shared актуально только в рамках C++11. В статье рассматривается C++03, я так понимаю.
А вот про что, имхо, следовало написать дополнительно, так это про то, что копирование shared_ptr может быть дорогим (QSharedPointer, std::shared_ptr) по этому не следует передавать его в функции по значению, за исключением случаев, когда это действительно надо.
boost::make_shared актуален и для c++
Афаик, boost::make_shared не делает хитрого финта ушами при использование make_shared — shared_ptr занимает одинаково памяти, как при использование make_shared, так и при использование конструктора.

По крайней мере на Going Native STL говорил, что уменьшение размера shared_ptr это MS-овское ноу-хау, которому еще предстоит переползти в реализации других вендорав shared_ptr.
Не из-за этого нужно использовать make_shared, не могу найти обсуждение этого, но вот 2 варианта кода:
void func(shared_ptr<A> a1, shared_ptr<A> a2);
func(shared_ptr<A>(new A()), shared_ptr<A>(new A()));

и
void func(shared_ptr<A> a1, shared_ptr<A> a2);
func(make_shared<A>(), make_shared<A>());

Первый вариант может привести к утечке памяти, второй правильный.
Да, вы чертовски правы. Применительно к auto_ptr об этом писал Саттер.
Касательно ноу-хау STL-я (человека) — ну так думаю этот патч в буст добавят очень быстро. Ибо boost::shared_ptr в ближайшие пару лет будет распространен значииииительно сильнее, чем std::shared_ptr в виду того, что первый поддерживается почти всеми компиляторами и кода с ним уже навалом, а последний только самыми свежими с поддержкой C++11 (или хотя бы TR1).
почему копирование дорогое? потому что надо счётчик при вызове addref защитить от других потоков?
Ох, а зачем такие расстояния между строками в листингах? Тяжко читается.
судя по всему, это из-за
white-space: pre-wrap;
в стилях Хабра.
>При первом вызове delete все отлично, при втором вы потрете не те данные.

Мне кажется там не стирается ничего.
Безопасно повторно удалять нулевой указатель, а ненулевой, указывающий на удаленный объект, — UB
Дабл делет это уб. Там может стираться, например, все содержимое Вашего жесткого диска.
Undefined Behavour не значит что комп может вдруг начать душить пользователя шнуром от мышки или вдруг стереть данные с диска. Иначе все эти «Программа совершила недопустимую операцию и будет закрыта» уже давно не оставили бы и следа от ваших данных.

Происходит на самом деле следующее. Указатель хранит адрес памяти. При вызове делит он должен его освободить, отдать обратно в систему. В случае если предыдущий вызов уже освободил этот адрес, то он уже отдан системе, а значит при повторной попытке освободить ресурс не принадлежащий вашей программе и происходит эта сама ошибка «данные не могут быть READ/WRITE» или что там еще.

Но бывает и иначе. К примеру память была освобождена, однако впоследствии вашей программе потребовалось еще немного памяти, система снова вам дала ее, и так совпало что та память которую вам вернула система — была та сама недавно удаленная. Событие не столь редкое как может показаться.
И тогда при двойном удалении — у нас возникает совершенно фееричная ситуация когда программа пытается удалить удаленную память, которая на самом деле заново выделенная и принадлежит совсем другому участку этой же программы. Вот тут начинается полный undefined bahavour.
К слову тоже самое может произойти не только с памятью но и с другим типом ресурсов — ведь почти все ресурсы внутри обозначаются как обычное целое число.
Представьте что вы работали с сокетом под номером 0xdf82, затем закрыли соединение, потом к вам пришло новое соединение, и вы начали работать с новым сокетом. Но то то что он новый не отменяет того факта что его идентификатор вполне может совпадать со старым, уже давно освобожденным значением.
Если же в какой-то переменной сохранилось старое значение, и где-то дальше по коду мы попробуем еще раз закрыть уже закрытое соединение ( чтобы наверняка, а то мало ли, кто эти компы знает ) то внезапно мы получим закрытие нашего нового соединения.
В случае с Вашей уютненькой вендой и Вашим уютненьким МС компилятором Вы корректно описали ситуацию, к которой приводит дабл делет. Но это не значит, что булгеон С++ компайлер из булгеон компайлер коллектион под БулгеонОС не отформатирует Ваш жесткий диск по дабл делет.

Конечно, дабл делет не приведет к форматированию жесткого диска в случае, когда вы пользуетесь распространенным C++ компилятором под распространенной ОС.
> Undefined Behavour не значит что комп может вдруг начать душить пользователя шнуром от мышки или вдруг стереть данные с диска.

Глупости. Все знают, что при UB ваша программа отсылает боссу письмо с ругательствами.
Если этот указатель (по которому делаем delete) нулевой, то да.
Если не нулевой — память будет помечена свободной. И кто знает, что туда будет записано при следующем такте процессора.
Под стиранием я имел в виду целенаправленное обнуление памяти или запись некого другого значения и собственно хотел это проянить. Судя потому что ответили ниже такого обычно не происходит.
Статья понравилась, легко читается. Никогда не писал на С++, но всё было понятно :)
Правильно ли я понимаю, что это мини garbage collector для С++?
Это управляемый garbage collector. В том смысле что вы явно указываете какая память и как будет освобождаться, и при неправильном использовании все равно можете получить ошибки связанные с памятью, например если создать на основании одной памяти два независимых умных указателя — они удаляют ее по очереди так как не будут знать друг про друга.
Это не является garbage collector, это совершенно другая технология (можно даже сказать, противоположная технология)
Это совсем не GC. Reference counting — один из подходов к решению задачи автоматического управления памятью. GC — другой подход.

больше похоже на automatic reference counting
Стоило бы отметить, что С++ позволяет создавать объекты в стеке, и тогда деструктор тоже вызывается автоматически, при выходе из scope, в котором создан объект.
А будет ли продолжение?
Интересует применение shared_ptr для массивов и наличие чего-то подобного для пары malloc/free.
Обновите, пожалуйста, пост, разметка кода пострадала (
Sign up to leave a comment.

Articles