Ответ 1
Функции, к которым вы обращаетесь, делают копию указателя. Поскольку вы не можете сделать копию unique_ptr
, нет смысла предоставлять эти функции для нее.
Как и в случае Boost, С++ 11 предоставляет некоторые функции для литья shared_ptr
:
std::static_pointer_cast
std::dynamic_pointer_cast
std::const_pointer_cast
Мне интересно, однако, почему нет функций эквивалентов для unique_ptr
.
Рассмотрим следующий простой пример:
class A { virtual ~A(); ... }
class B : public A { ... }
unique_ptr<A> pA(new B(...));
unique_ptr<A> qA = std::move(pA); // This is legal since there is no casting
unique_ptr<B> pB = std::move(pA); // This is not legal
// I would like to do something like:
// (Of course, it is not valid, but that would be the idea)
unique_ptr<B> pB = std::move(std::dynamic_pointer_cast<B>(pA));
Есть ли причина, почему этот шаблон использования не рекомендуется, и, таким образом, для unique_ptr
?
shared_ptr
Функции, к которым вы обращаетесь, делают копию указателя. Поскольку вы не можете сделать копию unique_ptr
, нет смысла предоставлять эти функции для нее.
В дополнение к Mark Ransom answer, unique_ptr<X, D>
может даже не хранить X*
.
Если делетер определяет тип D::pointer
, то то, что хранится и которое может не быть реальным указателем, оно должно удовлетворять требованиям NullablePointer
и (если unique_ptr<X,D>::get()
вызывается) имеет operator*
, который возвращает X&
, но не требуется поддерживать листинг для других типов.
unique_ptr
довольно гибкий и не обязательно ведет себя очень похоже на встроенный тип указателя.
В соответствии с запросом, вот пример, где хранимый тип не является указателем, и поэтому литье невозможно. Это немного надуманно, но завершает API созданной базы данных (определенный как API-интерфейс C) в API-интерфейсе С++ RAII. Тип OpaqueDbHandle удовлетворяет требованиям NullablePointer
, но только сохраняет целое число, которое используется как ключ для поиска фактического соединения с БД посредством определенного отображения, определенного реализацией. Я не показываю это как пример великолепного дизайна, как пример использования unique_ptr
для управления не скопированным подвижным ресурсом, который не является динамически выделенным указателем, где "deleter" не просто вызывает вызов деструктор и освободить память, когда unique_ptr
выходит за рамки.
#include <memory>
// native database API
extern "C"
{
struct Db;
int db_query(Db*, const char*);
Db* db_connect();
void db_disconnect(Db*);
}
// wrapper API
class OpaqueDbHandle
{
public:
explicit OpaqueDbHandle(int id) : id(id) { }
OpaqueDbHandle(std::nullptr_t) { }
OpaqueDbHandle() = default;
OpaqueDbHandle(const OpaqueDbHandle&) = default;
OpaqueDbHandle& operator=(const OpaqueDbHandle&) = default;
OpaqueDbHandle& operator=(std::nullptr_t) { id = -1; return *this; }
Db& operator*() const;
explicit operator bool() const { return id > 0; }
friend bool operator==(const OpaqueDbHandle& l, const OpaqueDbHandle& r)
{ return l.id == r.id; }
private:
friend class DbDeleter;
int id = -1;
};
inline bool operator!=(const OpaqueDbHandle& l, const OpaqueDbHandle& r)
{ return !(l == r); }
struct DbDeleter
{
typedef OpaqueDbHandle pointer;
void operator()(pointer p) const;
};
typedef std::unique_ptr<Db, DbDeleter> safe_db_handle;
safe_db_handle safe_connect();
int main()
{
auto db_handle = safe_connect();
(void) db_query(&*db_handle, "SHOW TABLES");
}
// defined in some shared library
namespace {
std::map<int, Db*> connections; // all active DB connections
std::list<int> unused_connections; // currently unused ones
int next_id = 0;
const unsigned cache_unused_threshold = 10;
}
Db& OpaqueDbHandle::operator*() const
{
return connections[id];
}
safe_db_handle safe_connect()
{
int id;
if (!unused_connections.empty())
{
id = unused_connections.back();
unused_connections.pop_back();
}
else
{
id = next_id++;
connections[id] = db_connect();
}
return safe_db_handle( OpaqueDbHandle(id) );
}
void DbDeleter::operator()(DbDeleter::pointer p) const
{
if (unused_connections.size() >= cache_unused_threshold)
{
db_disconnect(&*p);
connections.erase(p.id);
}
else
unused_connections.push_back(p.id);
}
Чтобы построить ответ на Дэйв, эта функция шаблона попытается переместить содержимое одного unique_ptr
в другой тип.
template <typename T_SRC, typename T_DEST, typename T_DELETER>
bool dynamic_pointer_move(std::unique_ptr<T_DEST, T_DELETER> & dest,
std::unique_ptr<T_SRC, T_DELETER> & src) {
if (!src) {
dest.reset();
return true;
}
T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
if (!dest_ptr)
return false;
std::unique_ptr<T_DEST, T_DELETER> dest_temp(
dest_ptr,
std::move(src.get_deleter()));
src.release();
dest.swap(dest_temp);
return true;
}
template <typename T_SRC, typename T_DEST>
bool dynamic_pointer_move(std::unique_ptr<T_DEST> & dest,
std::unique_ptr<T_SRC> & src) {
if (!src) {
dest.reset();
return true;
}
T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
if (!dest_ptr)
return false;
src.release();
dest.reset(dest_ptr);
return true;
}
Обратите внимание, что вторая перегрузка требуется для указателей, объявленных std::unique_ptr<A>
и std::unique_ptr<B>
. Первая функция не будет работать, потому что первый указатель будет фактически иметь тип std::unique_ptr<A, default_delete<A> >
, а второй из std::unique_ptr<A, default_delete<B> >
; типы дебетеров не будут совместимы, поэтому компилятор не позволит вам использовать эту функцию.
Это не ответ на вопрос, почему, но это способ сделать это...
std::unique_ptr<A> x(new B);
std::unique_ptr<B> y(dynamic_cast<B*>(x.get()));
if(y)
x.release();
Это не совсем чисто, так как на короткое время 2 unique_ptr
считают, что у них есть один и тот же объект. И, как было прокомментировано, вам также придется управлять перемещением пользовательского делетера, если вы используете один (но это очень редко).
Как насчет этого для подхода С++ 11:
template <class T_SRC, class T_DEST>
std::unique_ptr<T_DEST> unique_cast(std::unique_ptr<T_SRC> &&src)
{
if (!src) return std::unique_ptr<T_DEST>();
// Throws a std::bad_cast() if this doesn't work out
T_DEST *dest_ptr = &dynamic_cast<T_DEST &>(*src.get());
src.release();
return std::unique_ptr<T_DEST> ret(dest_ptr);
}
Если вы только собираетесь использовать указатель понижения в малой области видимости, одна альтернатива заключается в том, чтобы просто сбрасывать ссылку на объект, управляемый с помощью unique_ptr
:
auto derived = dynamic_cast<Derived&>(*pBase);
derived.foo();