агрессивный по сравнению с неразрушающим касательно - считаемые указатели в C++

В течение прошлых нескольких лет я имею общепринятый это

если я собираюсь использовать касательно - считаемые интеллектуальные указатели

агрессивные интеллектуальные указатели являются способом пойти

--

Однако я начинаю любить неразрушающие интеллектуальные указатели из-за следующего:

  1. Я только использую интеллектуальные указатели (так никакой Foo* лежащий вокруг, только Ptr)
  2. Я начинаю создавать пользовательские средства выделения для каждого класса. (Таким образом, Нечто перегрузило бы новый оператор).
  3. Теперь, если у Foo есть список всего Ptr (как он легко может с неразрушающими интеллектуальными указателями).
  4. Затем я могу избежать проблем фрагментации памяти начиная с класса, Foo перемещает объекты (и просто обновите соответствующий Ptr).

Единственная причина, почему этот Foo, перемещающий объекты в неразрушающих интеллектуальных указателях, являющихся легче, чем агрессивные интеллектуальные указатели:

В неразрушающих интеллектуальных указателях существует только один указатель, который указывает каждому Foo.

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

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

У кого-либо есть хорошее исследование дорогих, которые этот дополнительный слой косвенности?

Править: интеллектуальными указателями я могу обращаться к тому, что другие называют "общими указателями"; вся эта мысль: существует подсчет ссылок, присоединенный к объектам, и когда он совершает нападки 0, объект автоматически удален

10
задан anon 21 March 2010 в 09:59
поделиться

4 ответа

Есть несколько важных различий между инвазивными и неинвазивными указателями:

Самое большое преимущество второго (неинвазивного):

  • Это Намного проще реализовать слабую ссылку на вторую (т.е. shared_ptr / weak_ptr ).

Преимущество первого - когда вам нужно получить на него умный указатель (по крайней мере, в случае boost :: shared_ptr , std :: tr1 :: shared_ptr )

  • Вы не можете использовать shared_ptr из этого в конструкторе и деструкторе.
  • Довольно нетривиально иметь shared_from this в иерархии классов.
8
ответ дан 3 December 2019 в 17:19
поделиться

Я не знаю исследования о дополнительных затратах из-за неинвазивного, а не инвазивного. Но я хотел бы отметить, что неинвазивный метод, кажется, универсально рекомендован экспертами по 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 * , и поэтому голые указатели все еще могут просочиться, часто довольно труднодоступными способами.

5
ответ дан 3 December 2019 в 17:19
поделиться

Ну, в первую очередь, я напомню вам, что совместное владение обычно сложно приручить, и оно может привести к довольно сложному устранению ошибок.

Есть много способов отказаться от долевого владения. Подход 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 .

7
ответ дан 3 December 2019 в 17:19
поделиться

Единственная реальная стоимость неинвазивного подсчета реф. производительность заключается в том, что вам иногда требуется одно дополнительное выделение для счетчика ссылок. Насколько мне известно, реализации tr1 :: shared_ptr не делают «двойного косвенного обращения». Я полагаю, было бы трудно поддерживать преобразования, не позволяя shared_ptr напрямую хранить указатель. Разумная реализация shared_ptr будет хранить два указателя: один указатель на объект (без двойного косвенного обращения) и один указатель на некоторую управляющую структуру.

Даже накладные расходы на выделение ресурсов не нужны во всех ситуациях. См. make_shared . C ++ 0x также предоставляет функцию make_shared , которая выделяет объект и счетчик ссылок за один раз, что аналогично альтернативе навязчивого подсчета ссылок.

[...] Помимо удобства и стиля, такая функция также безопасна в исключительных случаях и значительно быстрее, поскольку может использовать один выделение как для объекта, так и для соответствующего ему блока управления, что устраняет значительную часть накладных расходов на построение shared_ptr. Это устраняет одну из основных претензий к эффективности shared_ptr. [...]

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

3
ответ дан 3 December 2019 в 17:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: