В течение прошлых нескольких лет я имею общепринятый это
если я собираюсь использовать касательно - считаемые интеллектуальные указатели
агрессивные интеллектуальные указатели являются способом пойти
--
Однако я начинаю любить неразрушающие интеллектуальные указатели из-за следующего:
Единственная причина, почему этот Foo, перемещающий объекты в неразрушающих интеллектуальных указателях, являющихся легче, чем агрессивные интеллектуальные указатели:
В неразрушающих интеллектуальных указателях существует только один указатель, который указывает каждому Foo.
В агрессивных интеллектуальных указателях я понятия не имею, сколько объектов указывает каждому Foo.
Теперь, единственная стоимость неразрушающих интеллектуальных указателей... является двойной косвенностью. [Возможно, это завинчивает кэши].
У кого-либо есть хорошее исследование дорогих, которые этот дополнительный слой косвенности?
Править: интеллектуальными указателями я могу обращаться к тому, что другие называют "общими указателями"; вся эта мысль: существует подсчет ссылок, присоединенный к объектам, и когда он совершает нападки 0, объект автоматически удален
Есть несколько важных различий между инвазивными и неинвазивными указателями:
Самое большое преимущество второго (неинвазивного):
shared_ptr
/ weak_ptr
). Преимущество первого - когда вам нужно получить на него умный указатель (по крайней мере, в случае boost :: shared_ptr
, std :: tr1 :: shared_ptr
)
shared_ptr
из этого в конструкторе и деструкторе. Я не знаю исследования о дополнительных затратах из-за неинвазивного, а не инвазивного. Но я хотел бы отметить, что неинвазивный метод, кажется, универсально рекомендован экспертами по C ++. Конечно, это может ничего не значить! Но рассуждение довольно здравое: если вам нужны умные указатели, это потому, что вам нужен более простой способ реализовать управление временем жизни объекта, а не писать его вручную, поэтому вы подчеркиваете правильность и простоту над производительностью, что всегда является хорошей идеей, пока вы не профилировали реалистичную модель всего вашего дизайна.
Вполне может быть, что в упрощенном тесте неинвазивный метод в два раза медленнее, чем инвазивный, но все же в реальной программе, которая выполняет реальную работу, эта разница в скорости теряется в шуме и становится настолько незначительной, что вы не можете даже измерить это. Это довольно распространенное явление; то, что вы считаете важным для производительности, очень часто не так.
Если вы обнаружите узкое место в производительности, возможно (вероятно?), Что работа по поддержанию самого счетчика ссылок (в обоих подходах) будет иметь такое же влияние на производительность, как и дополнительное косвенное обращение в неинвазивном подходе. С необработанными указателями оператор:
p1 = p2;
, вероятно, должен только переместить значение между двумя регистрами ЦП после того, как оптимизатор сработает. Но если они являются интеллектуальными указателями с подсчетом ссылок, то даже при инвазивном использовании это выглядит примерно так:
if (p1 != p2)
{
if ((p1 != 0) && (--(p1->count) == 0))
delete p1;
p1 = p2;
if (p1 != 0)
p1->count++;
}
Это происходит с каждым аргументом интеллектуального указателя, переданным каждой функции. Таким образом, существует множество дополнительных обращений к потенциально удаленным областям памяти, чтобы каждый раз увеличивать и уменьшать счет.А чтобы быть поточно-ориентированными, операции увеличения и уменьшения должны быть взаимосвязаны / атомарны, что может оказать серьезное негативное влияние на несколько ядер.
Я считаю «сладким пятном» C ++ те ситуации, когда вам не нужно управлять такими динамичными структурами данных, как эта. Вместо этого у вас есть простая иерархическая структура владения объектами, поэтому существует очевидный единственный владелец каждого объекта, а время жизни данных имеет тенденцию следовать за временем жизни вызовов функций (чаще всего). Затем вы можете позволить стандартным контейнерам и стеку вызовов функций управлять всем за вас. Это подчеркивается в готовящейся к выпуску версии языка со ссылками на rvalue, unique_ptr
и т. Д., Которые предназначены для простой передачи единоличного владения объектом. Если вам действительно нужно динамическое управление временем жизни с несколькими владельцами, настоящий GC будет быстрее и проще в правильном использовании, но C ++ не очень удачный дом для GC.
Еще один незначительный момент: к сожалению, «В неинвазивных интеллектуальных указателях есть только один указатель, указывающий на каждый Foo», неверно. Внутри Foo
есть указатель this
, который является Foo *
, и поэтому голые указатели все еще могут просочиться, часто довольно труднодоступными способами.
Ну, в первую очередь, я напомню вам, что совместное владение обычно сложно приручить, и оно может привести к довольно сложному устранению ошибок.
Есть много способов отказаться от долевого владения. Подход Factory
(реализованный сам с помощью контейнера указателя ускорения ) лично мне один из моих любимых.
Теперь, что касается подсчета ссылок ....
1. Навязчивые указатели
Счетчик встроен в сам объект, что означает:
weak_ptr
, поэтому у вас не может быть циклов ссылок в вашем проекте без использования шаблона Observer
... довольно сложно 2. Неинтрузивные указатели
Я буду говорить только о boost :: shared_ptr
и boost :: weak_ptr
. Я немного недавно покопался в источнике, чтобы точно взглянуть на механику, и действительно есть намного более сложные, чем описанные выше!
// extract of <boost/shared_ptr.hpp>
template <class T>
class shared_ptr
{
T * px; // contained pointer
boost::detail::shared_count pn; // reference counter
};
weak_ptr
в случае циклических ссылок. shared_ptr
, должен знать о деструкторе объекта (см. Пример) Вот небольшой пример, чтобы проиллюстрировать эту магию прямого объявления:
// foofwd.h
#include <boost/shared_ptr.hpp>
class Foo;
typedef boost::shared_ptr<Foo> foo_ptr;
foo_ptr make_foo();
// foo.h
#include "foofwd.h"
class Foo { /** **/ };
// foo.cpp
#include "foo.h"
foo_ptr make_foo() { return foo_ptr(new Foo()); }
// main.cpp
#include "foofwd.h"
int main(int argc, char* argv[])
{
foo_ptr p = make_foo();
} // p.get() is properly released
Есть немного немного магии шаблонов, чтобы разрешить это. В основном объект счетчика включает в себя средство удаления *
(еще третье распределение), которое позволяет выполнять стирание некоторых типов. Это действительно полезно, так как позволяет использовать предварительное объявление!
3. Заключение
Хотя я согласен с тем, что навязчивые указатели, вероятно, работают быстрее, поскольку происходит меньшее выделение (есть 3 разных блока памяти, выделенных для shared_ptr
), они также менее практичны.
Итак, я хотел бы указать вам на библиотеку Boost Intrusive Pointer , и особенно на ее введение:
Как правило, если не очевидно,
intrusive_ptr
лучше соответствует вашим потребностям, чемshared_ptr
, сначала попробуйте проект на основеshared_ptr
.
Единственная реальная стоимость неинвазивного подсчета реф. производительность заключается в том, что вам иногда требуется одно дополнительное выделение для счетчика ссылок. Насколько мне известно, реализации tr1 :: shared_ptr не делают «двойного косвенного обращения». Я полагаю, было бы трудно поддерживать преобразования, не позволяя shared_ptr напрямую хранить указатель. Разумная реализация shared_ptr будет хранить два указателя: один указатель на объект (без двойного косвенного обращения) и один указатель на некоторую управляющую структуру.
Даже накладные расходы на выделение ресурсов не нужны во всех ситуациях. См. make_shared . C ++ 0x также предоставляет функцию make_shared
, которая выделяет объект и счетчик ссылок за один раз, что аналогично альтернативе навязчивого подсчета ссылок.
[...] Помимо удобства и стиля, такая функция также безопасна в исключительных случаях и значительно быстрее, поскольку может использовать один выделение как для объекта, так и для соответствующего ему блока управления, что устраняет значительную часть накладных расходов на построение shared_ptr. Это устраняет одну из основных претензий к эффективности shared_ptr. [...]
В свете shared_ptr
и make_shared
мне трудно столкнуться с проблемами, когда навязчивый умный указатель мог бы превзойти shared_ptr
существенно. Однако копирование и уничтожение общих указателей может быть немного медленнее. Сказав это, позвольте мне добавить, что я редко использую такие умные указатели. В большинстве случаев все, что мне нужно - это уникальное владение.