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

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

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

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

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

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

По крайней мере на Going Native STL говорил, что уменьшение размера shared_ptr это MS-овское ноу-хау, которому еще предстоит переползти в реализации других вендорав shared_ptr.
0
Не из-за этого нужно использовать 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>());

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

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

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

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

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

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

0
Стоило бы отметить, что С++ позволяет создавать объекты в стеке, и тогда деструктор тоже вызывается автоматически, при выходе из scope, в котором создан объект.
0
А будет ли продолжение?
Интересует применение shared_ptr для массивов и наличие чего-то подобного для пары malloc/free.
Only those users with full accounts are able to leave comments., please.