Подтвердить что ты не робот

Каков наилучший способ реализации интеллектуальных указателей на С++?

Я оцениваю различные реализации интеллектуальных указателей (ничего себе, там много), и мне кажется, что большинство из них можно разделить на две широкие классификации:

1) Эта категория использует наследование на объектах, на которые ссылаются, так что они имеют ссылочные подсчеты и обычно выполняют up() и down() (или их эквиваленты). IE, чтобы использовать интеллектуальный указатель, объекты, на которые вы указываете, должны наследовать от некоторого класса, который предоставляет ref-реализация.

2) В этой категории используется вторичный объект для хранения ссылок. Например, вместо того, чтобы указывать интеллектуальный указатель прямо на объект, он фактически указывает на этот объект метаданных... У кого есть эталоны ссылок и up() и down() (и которые обычно предоставляют механизм для указателя на получить на фактический объект, на который указывают, так что интеллектуальный указатель может правильно реализовать оператор → ()).

Теперь у 1 есть недостаток, что он заставляет все объекты, которые вы хотели бы ссылаться на счет, наследовать от общего предка, а это означает, что вы не можете использовать это для ссылок на объекты подсчета, которые у вас нет контроля над исходный код.

2 имеет проблему, поскольку, поскольку счетчик хранится в другом объекте, если у вас когда-либо была ситуация, когда указатель на текущий объект подсчитанных ссылок преобразуется в ссылку, у вас, вероятно, есть ошибка (IE, поскольку счетчик не находится в фактическом объекте, нет никакой возможности для новой ссылки, чтобы получить счет... ref для создания или назначения копии копии, потому что они могут совместно использовать объект count, но если вам когда-либо понадобится преобразовать из указателя, ты полностью хочешь)...

Теперь, насколько я понимаю, boost:: shared_pointer использует механизм 2 или что-то в этом роде... Тем не менее, я не могу решить, что хуже! Я только когда-либо использовал механизм 1, в производственном коде... У кого-нибудь есть опыт работы с обоими стилями? Или, может быть, есть другой способ, который лучше, чем оба этих?

4b9b3361

Ответ 1

"Каков наилучший способ реализации интеллектуальных указателей в С++"

  • Не использовать! Использовать существующий, хорошо проверенный смарт-указатель, такой как boost:: shared_ptr или std:: tr1:: shared_ptr (std:: unique_ptr и std:: shared_ptr с С++ 11 )
  • Если вам нужно, помните:
    • использовать safe-bool idiom
    • предоставить оператор →
    • предоставляет надежную гарантию исключения
    • задокументировать требования к исключениям, которые ваш класс делает на удаленном сервере
    • используйте copy-modify-swap, где это возможно, для реализации надежной гарантии.
    • правильно ли вы обрабатываете многопоточность.
    • написать обширные модульные тесты
    • реализовать преобразование на базу таким образом, чтобы оно удалялось по типу производного указателя (интеллектуальные указатели интеллектуальных указателей/интеллектуальных указателей дефолта).
    • поддержка получения доступа к необработанному указателю
    • рассмотреть стоимость/выгоду предоставления слабых указателей для прерывания циклов
    • предоставить соответствующие операторы кастинга для ваших интеллектуальных указателей.
    • сделать ваш конструктор шаблоном для обработки строкового указателя базы из производного.

И не забывайте ничего, что я, возможно, забыл в вышеупомянутом неполном списке.

Ответ 2

Просто чтобы предоставить другой взгляд на вездесущий ответ Boost (хотя это правильный ответ для многих применений), посмотрите Loki внедрение интеллектуальных указателей. Для дискурса философии дизайна первоначальный создатель Локи написал книгу Современный дизайн С++.

Ответ 3

Я использую boost:: shared_ptr уже несколько лет, и пока вы правы в отношении недостатков (без назначения с помощью указателя), я думаю, что это определенно стоило этого из-за огромного количества ошибок, связанных с указателями спас меня от.

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

Ответ 4

Boost также имеет интрузивный указатель (например, решение 1), который не требует наследования чего-либо. Это требует изменения указателя на класс для хранения счетчика ссылок и предоставления соответствующих функций-членов. Я использовал это в случаях, когда важна эффективность памяти, и не требовал накладных расходов другого объекта для каждого используемого общего указателя.

Пример:

class Event {
public:
typedef boost::intrusive_ptr<Event> Ptr;
void addRef();
unsigned release();
\\ ...
private:
unsigned fRefCount;
};

inline void Event::addRef()
{
  fRefCount++;
}
inline unsigned Event::release(){
    fRefCount--;
    return fRefCount;
}

inline void intrusive_ptr_add_ref(Event* e)
{
  e->addRef();
}

inline void intrusive_ptr_release(Event* e)
{
  if (e->release() == 0)
  delete e;
}

Ptr typedef используется, чтобы я мог легко переключаться между boost:: shared_ptr < > и boost:: intrusive_ptr < > без изменения кода клиента

Ответ 5

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

  • Общие: в тех случаях, когда владение распределяется между несколькими объектами
  • Принадлежит: Где один объект владеет объектом, но передача разрешена.
  • Unmovable: объект, которому принадлежит объект, и его нельзя передать.

Стандартная библиотека имеет:

  • станд:: auto_ptr

Boost имеет пару больше, чем был адаптирован tr1 (следующая версия стандарта)

  • станд:: tr1:: shared_ptr
  • станд:: tr1:: weak_ptr

И те, кто все еще находится в boost (что в относительной мере должно быть в любом случае), которые, надеюсь, превратят его в tr2.

  • подталкивание:: scoped_ptr
  • boost::scoped_array
  • подталкивание:: shared_array
  • подталкивание:: intrusive_ptr

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

Ответ 6

Мне кажется, этот вопрос похож на вопрос: "Какой алгоритм наилучшего сортирования?" Нет ни одного ответа, это зависит от ваших обстоятельств.

В моих собственных целях я использую ваш тип 1. У меня нет доступа к библиотеке TR1. У меня есть полный контроль над всеми классами, в которых мне нужны общие указатели. Дополнительная память и эффективность времени для типа 1 могут быть довольно незначительными, но использование памяти и скорость - большие проблемы для моего кода, поэтому тип 1 был slam dunk.

С другой стороны, для тех, кто может использовать TR1, я думаю, что класс 2 std:: tr1:: shared_ptr был бы разумным выбором по умолчанию, который будет использоваться всякий раз, когда нет какой-либо насущной причины, чтобы не используйте его.

Ответ 7

Проблема с 2 может быть решена. Boost предлагает boost:: shared_from_this по этой же причине. На практике это не большая проблема.

Но причина, по которой они пошли с вашим вариантом № 2, заключается в том, что его можно использовать во всех случаях. Опираясь на наследование не всегда является опцией, а затем у вас остается умный указатель, который вы не можете использовать для половины кода.

Я бы сказал, что # 2 лучше, просто потому, что он может быть использован в любых обстоятельствах.

Ответ 8

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

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

void doSomething (NIPtr<int> const &);

void foo () {
  NIPtr<int> i = new int;
  int & j = *i;
  doSomething (&j);          // Ooops - owned by two pointers! :(
}

Некоторое время назад некоторые рефакторинги приводили к тому, что некоторые части кода были объединены, и поэтому необходимо было сделать выбор относительно того, какой тип указателя использовать. У неинтрузивного указателя теперь был конструктор преобразования, объявленный как явный, и поэтому было решено пойти с интрузивным указателем, чтобы сохранить требуемое количество изменений кода.

К нашему большому удивлению, мы заметили, что у нас было немедленное улучшение производительности с помощью интрузивного указателя. Мы не проводили много исследований в этом и просто предположили, что разница в стоимости поддержания объекта count. Возможно, что другие реализации неинтрузивного общего указателя уже решили эту проблему.

Ответ 9

О чем вы говорите, это умные указатели intrusive и неинтрузивные. У Boost есть и то, и другое. boost::intrusive_ptr вызывает функцию для уменьшения и увеличения счетчика ссылок вашего объекта, каждый раз, когда ему нужно изменить счетчик ссылок. Это не вызывает функции-члены, а бесплатные функции. Таким образом, он позволяет управлять объектами без необходимости изменять определение их типов. И, как вы говорите, boost::shared_ptr не является навязчивым, ваша категория 2.

У меня есть ответ, объясняющий intrusive_ptr: Сделать shared_ptr не использовать delete. Короче говоря, вы используете его, если у вас есть объект, у которого уже есть подсчет ссылок, или необходимо (как вы объясните) объект, на который уже ссылается принадлежать intrusive_ptr.