2016-03-20 3 views
9

Я понимаю, что использование static_pointer_cast с unique_ptr приведет к совместному владению содержащимися данными.
Другими словами, то, что я хотел бы сделать, это:Альтернативы static_pointer_cast для unique_ptr

unique_ptr<Base> foo = fooFactory(); 
// do something for a while 
unique_ptr<Derived> bar = static_unique_pointer_cast<Derived>(foo); 

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

До сих пор в тех случаях, когда я хочу хранить указатели для этих базовых классов, но мне также необходимо отнести их к некоторым производным классам (например, представьте сценарий, связанный с стиранием стилей), я использовал shared_ptr s из-за того, что я упомянул выше.

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

+0

' static_pointer_cast' определяется только для аргумента типа 'std :: shared_ptr ' - он вообще не применим с 'unique_ptr' –

+0

@MM Да, я знаю, я написал почти то же самое в вопросе о том, что не существует эквивалента для 'unique_ptr'. – skypjack

+0

Почему стирание типа приводит к снижению? Если вам необходимо применить базовый класс к производному типу, то чаще всего, за исключением очень небольшого числа случаев, проблема дизайна должна решаться по-разному. – Jens

ответ

16

Сырье указателей

Решение вашей проблемы, чтобы получить необработанный (не владеющий) указатель и брось - то просто дайте сырой указателю выйти за рамки и пусть остальные unique_ptr<Base> constrol времени жизни принадлежащего государству.

Как это:

unique_ptr<Base> foo = fooFactory(); 

{ 
    Base* tempBase = foo.get(); 
    Derived* tempDerived = static_cast<Derived*>(tempBase); 
} //tempBase and tempDerived go out of scope here, but foo remains -> no need to delete 

Unique_pointer_cast

Другой вариант заключается в использовании release() функцию unique_ptr, чтобы обернуть его в другой unique_ptr.

Как это

template<typename TO, typename FROM> 
unique_ptr<TO> static_unique_pointer_cast (unique_ptr<FROM>&& old){ 
    return unique_ptr<TO>{static_cast<TO*>(old.release())}; 
    //conversion: unique_ptr<FROM>->FROM*->TO*->unique_ptr<TO> 
} 

unique_ptr<Base> foo = fooFactory(); 

unique_ptr<Derived> foo2 = static_unique_pointer_cast<Derived>(std::move(foo)); 

Помните, что это аннулирует старый указатель foo

Ссылки из сырых указателей

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

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

Пример:

unique_ptr<Base> foo = fooFactory(); 
Derived& bar = *(static_cast<Derived*>(foo.get())); 
//do not use bar after foo goes out of scope 
+1

Почему бы не обратить вспять порядок этих параметров шаблона и вывести 'FROM', как и в' std :: static_pointer_cast'? – aschepler

+0

@ Anedar Да, очевидно, я не хочу иметь дело с необработанными указателями, но в любом случае вы дали мне хорошую подсказку ... Реализация 'static_unique_pointer_cast' интересна, но, к сожалению, не соответствует идее (выраженной в вопрос) о сохранении этих указателей, поскольку, как только они были аннулированы, они должны быть повторно инициализированы для * будущих использования * после текущего использования, поэтому это приведет к огромной работе вокруг контейнера. – skypjack

+0

хорошая точка, @aschepler, я добавил это. – Anedar

6

Я понимаю, что с помощью static_pointer_cast с unique_ptr привело бы к общей собственности содержащихся данных.

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

Если вы не хотите передавать право собственности, просто используйте необработанный указатель.

Или, если вы хотите двух владельцев, используйте shared_ptr.

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

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

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

Это не так для shared_ptr, где «очевидное» решение не делать правильные вещи:

shared_ptr<Derived> p2(static_cast<Derived*>(p1.get()); 

Это создало бы две различные shared_ptr объекты, которые владеют тот же указатель, но дон (т. е. они оба попытаются удалить его, что приведет к неопределенному поведению).

Когда shared_ptr был впервые стандартизован, не было безопасного способа сделать это, поэтому были определены static_pointer_cast и соответствующие функции каста. Они нуждались в доступе к деталям реализации информации бухгалтерского учета shared_ptr для работы.

Однако в процессе shared_ptr стандартизации C++ 11 были расширены за счет добавления «наложения спектров конструктору», который позволяет сделать бросок просто и безопасно:

shared_ptr<Derived> p2(p1, static_cast<Derived*>(p1.get()); 

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

+0

Ну, * Это не существует, потому что это тривиально, чтобы написать это самостоятельно *, может быть применено также к некоторым другим частям (которые существуют) STL, но я получил точку вашего ответа. +1 – skypjack

+0

Вы пропустили конец предложения, «если вам это нужно». Вещи стоит вставить в стандарт, если они трудны/невозможны для пользователей, чтобы писать правильно (что было верно для оригиналов shared_ptr изначально), или если они легки, но все продолжают переопределять его. Ни одно из них не верно для casting unique_ptr, потому что это легко, но не часто необходимо. –

+1

Я согласен, честно говоря, я думаю, что сталкиваюсь с проблемой XY, но мне интересен вопрос, что другие могли бы сделать, если бы они были мной. – skypjack

0

Я хотел бы добавить что-то к предыдущему ответу Anedar, который вызывает метод-член release() данного std::unique_ptr<U>. Если требуется реализовать также dynamic_pointer_cast (в дополнение к static_pointer_cast) для преобразования std::unique_ptr<U> в std::unique_ptr<T>, необходимо убедиться, что ресурс, защищенный уникальным указателем, освобожден должным образом в случае сбоя dynamic_cast (т. Е. Возвращает nullptr). В противном случае происходит утечка памяти.

Код:

#include <iostream> 
#include <memory> 

template< typename T, typename U > 
inline std::unique_ptr<T> dynamic_pointer_cast(std::unique_ptr<U> &&ptr) { 
    U * const stored_ptr = ptr.release(); 
    T * const converted_stored_ptr = dynamic_cast< T * >(stored_ptr); 
    if (converted_stored_ptr) { 
     std::cout << "Cast did succeeded\n"; 
     return std::unique_ptr<T>(converted_stored_ptr); 
    } 
    else { 
     std::cout << "Cast did not succeeded\n"; 
     ptr.reset(stored_ptr); 
     return std::unique_ptr<T>(); 
    } 
} 

struct A { 
    virtual ~A() = default; 
}; 
struct B : A { 
    virtual ~B() { 
     std::cout << "B::~B\n"; 
    } 
}; 
struct C : A { 
    virtual ~C() { 
     std::cout << "C::~C\n"; 
    } 
}; 
struct D { 
    virtual ~D() { 
     std::cout << "D::~D\n"; 
    } 
}; 

int main() { 

    std::unique_ptr<A> b(new B); 
    std::unique_ptr<A> c(new C); 
    std::unique_ptr<D> d(new D); 

    std::unique_ptr<B> b1 = dynamic_pointer_cast< B, A >(std::move(b)); 
    std::unique_ptr<B> b2 = dynamic_pointer_cast< B, A >(std::move(c)); 
    std::unique_ptr<B> b3 = dynamic_pointer_cast< B, D >(std::move(d)); 
} 

выход (возможен заказ):

Cast did succeeded 
Cast did not succeeded 
Cast did not succeeded 
B::~B 
D::~D 
C::~C 

деструкторы C и D не будет вызываться, если один использует:

template< typename T, typename U > 
inline std::unique_ptr<T> dynamic_pointer_cast(std::unique_ptr<U> &&ptr) { 
    return std::unique_ptr<T>(dynamic_cast< T * >(ptr.release())); 
} 
Смежные вопросы