Каким образом интеллектуальные указатели должны быть сброшены?

Обрабатывают ли интеллектуальные указатели приведение вниз, и если нет, то как безопасно обойти это ограничение?

Примером того, что я пытаюсь сделать, является наличие двух векторов STL (например), содержащих интеллектуальные указатели. Первый содержит интеллектуальные указатели на базовый класс, а второй - интеллектуальные указатели на производный класс. Ссылки на интеллектуальные указатели подсчитываются, например поведение похоже на shared_ptrs Boost, но вручную. Я включил пример кода, который я придумал, чтобы предоставить пример:

vector<CBaseSmartPtr> vecBase;
vector<CDerivedSmartPtr> vecDer;
...
CBaseSmartPtr first = vecBase.front();
vecDer.push_back(CDerivedSmartPtr(dynamic_cast<CDerived*>(first.get()));

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

Я бы надеялся, но не думаю, что это сработает, - это прямое приведение вниз с сохранением того же объекта, например.

dynamic_cast<CDerivedSmartPtr>(first)

Следует ли мне изменить второй контейнер, чтобы он также использовал CBaseSmartPtr и понижал только использование? Есть ли другие решения?


person dlanod    schedule 02.09.2009    source источник
comment
Я думаю, что это интересный вопрос, потому что он касается внутренней части реализаций интеллектуальных указателей. Но я также хотел бы отметить, что встраивание понижения в вашу стратегию подразумевает недостаток дизайна, ИМХО.   -  person Peter Cardona    schedule 02.09.2009
comment
Чтобы ответить, почему я делаю свой собственный, это унаследованный код, и убрать текущее использование и заменить его на Boost было бы довольно большим изменением. Однако это в моем списке будущих вещей, на которые стоит обратить внимание.   -  person dlanod    schedule 02.09.2009
comment
Обычные интеллектуальные указатели не имеют проблем с совместным владением; на самом деле обычный интеллектуальный указатель Boost даже называется shared_ptr. Ууу, это проблема, когда два интеллектуальных указателя совместно владеют правами собственности?   -  person MSalters    schedule 02.09.2009
comment
@MSalters: first.get() может означать, что они не знают, что должны делиться.   -  person sbi    schedule 02.09.2009
comment
Я не понимаю, почему вырывать и заменять на boost. Shared_ptr - это более крупное и радикальное изменение, чем вырывание и замена моей собственной самодельной копией того же   -  person jalf    schedule 03.09.2009
comment
@jalf: Фактически, с boost вам нужно только искать ошибки при извлечении и замене, а не в интеллектуальном указателе.   -  person sbi    schedule 03.09.2009


Ответы (5)


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

Первое, что вам нужно исправить, - это счетчик. Поскольку вам может потребоваться совместное использование счетчика между smart_ptr<Base> и smart_ptr<Derived>, тип счетчика не должен зависеть от аргумента типа. В общем, это все равно не имеет большого значения. Счетчик - это просто size_t, вероятно, заключенный в класс. (Примечание: существуют альтернативные конструкции интеллектуальных указателей, но вопрос настоятельно предполагает, что используется счетчик)

Бросок к базе должен быть довольно тривиальным. Следовательно, ваш smart_ptr должен иметь конструктор, принимающий smart_ptr. В этом ctor добавьте строку static_cast<T*>((U*)0);. Это не генерирует код, но предотвращает создание экземпляра, когда T не является основанием для U (квалификации по модулю const).

Обратное должно быть явным приведением. Вы не можете программно перечислить все основания T, поэтому smart_ptr<T> не может быть производным от smart_ptr<Base1_of_T>, smart_ptr<Base2_of_T>, ... Следовательно, dynamic_cast<smart_ptr<T> > не будет работать. Вы можете предоставить свой собственный smart_dynamic_cast<SPT>(smart_ptr<U> const& pU). Лучше всего это реализовать как функцию, возвращающую SPT. В этой функции вы можете просто выполнить return SPT(dynamic_cast<SPT::value_type*>(&*pU)).

person MSalters    schedule 02.09.2009
comment
Собственно, я думаю, что программно перечислить базы нельзя. Разве Андрей не показал нам, как это сделать? - person sbi; 02.09.2009
comment
Есть книга, но не здесь. Но я такого не помню. Просто чтобы устранить путаницу: цель перечисления баз состоит в том, чтобы определить набор типов B1..Bn с учетом типа D, так что отношение is_base_and_derived<B,D> выполняется для всех типов B1..Bn и никаких других типов. - person MSalters; 02.09.2009
comment
@MSalters: Ах, мой мозг. Я думал о перечислении типов и создании их базового класса ... Извините. - person sbi; 02.09.2009

Требуемое свойство - это ковариация в указанном типе. То есть, если D - это B, тогда вам нужно smartptr<D> isa smartptr<B>. Я не думаю, что это вообще элегантно поддерживается в C ++, но, как всегда, доступны хаки с шаблонами / перегрузками.

http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/pointer_cast.html дает динамическое приведение, которое работает с обычным и boost :: smart_ptr. Вам следует извлечь уроки из реализации, если вы не хотите использовать только Boost.

person Jonathan Graehl    schedule 02.09.2009
comment
+1 для dynamic_pointer_cast и -1, чтобы сказать, что C ++ не имеет ковариантных типов возврата. См. Здесь: parashift.com/c++-faq-lite /virtual-functions.html#faq-20.8 - person Michael Kristofik; 02.09.2009
comment
@Kristo: Я с тобой по обоим, но я все еще думаю, что это очень хороший совет. @ wang-wang: Почему бы тебе не удалить ложное утверждение? Было бы проще проголосовать за этот, в остальном хороший, ответ. - person sbi; 02.09.2009
comment
Вы правы - я не понимал, что виртуальные методы допускают ковариантные возвращаемые типы! Но это правда, что общие типы, например ptr ‹A› vector ‹A› не поддерживает ковариацию (как это делает Java, хотя и по стиранию типа). - person Jonathan Graehl; 02.09.2009
comment
@ wang-wang: Да, преобразование std::vector<Derived*> в std::vector<Base*> невозможно с использованием только основного языка. Я тоже считаю это недостатком C ++. - person sbi; 03.09.2009

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

person Abhay    schedule 02.09.2009
comment
Это выглядит совершенно устрашающе. Он транслирует shared_ptr ‹T› * на shared_ptr ‹U› *. См. Также ответ Райнера Дейка. - person MSalters; 02.09.2009

Обычные интеллектуальные указатели, такие как std::auto_ptr, небезопасно использовать в контейнерах STL из-за того, что владение перемещается, когда STL назначает экземпляры интеллектуальных указателей друг другу при копировании данных внутри. Вместо этого вам нужно использовать что-то вроде boost::shared_ptr, которое внутренне реализует подсчет ссылок, чтобы гарантировать, что объект остается живым, независимо от того, сколько экземпляров интеллектуальных указателей ссылаются на него. Если вы пишете свои собственные типы интеллектуальных указателей, вам необходимо реализовать аналогичный подсчет ссылок.

person Remy Lebeau    schedule 02.09.2009
comment
Ура, я добавлю комментарий к вопросу, в котором будет четко указано, что я планирую использовать интеллектуальные указатели с подсчетом ссылок. - person dlanod; 02.09.2009

Я нашел это на страницах Microsoft:

    std::shared_ptr<base> sp0(new derived); 
    std::shared_ptr<derived> sp1 = 
    std::dynamic_pointer_cast<derived>(sp0); 
person stjepano    schedule 30.10.2011