为unique_ptrdynamic投射
正如Boost中的情况一样,C ++ 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));
有没有什么理由不鼓励这种使用模式,因此,与shared_ptr
中存在的函数相同的函数不提供给unique_ptr
?
你引用的每个函数都会复制一个指针。 既然你不能创build一个unique_ptr
的副本,为它提供这些function是没有意义的。
除了Mark Ransom的回答之外 , unique_ptr<X, D>
甚至可能不存储X*
。
如果删除器定义了typesD::pointer
那么这就是存储的内容,这可能不是一个真正的指针,它只需要满足NullablePointer
要求(如果调用unique_ptr<X,D>::get()
),有一个返回X&
的operator*
,但不需要支持对其他types的转换。
unique_ptr
非常灵活,不一定非常像内置的指针types。
根据要求,下面是一个存储types不是指针的例子,因此不可能进行转换。 这是一个有点人为的做法,但是在C ++ RAII风格的API中封装了一个构build好的数据库API(定义为C风格的API)。 OpaqueDbHandletypes符合NullablePointer
要求,但只存储一个整数,它被用作一个键,通过一些实现定义的映射来查找实际的DB连接。 我没有把这个作为一个伟大devise的例子,就像使用unique_ptr
来pipe理不可复制的,不是dynamic分配指针的可移动资源的一个例子,其中“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); }
为了build立在Dave的答案上,这个模板函数将尝试将一个unique_ptr
的内容移动到另一个不同types的内容。
- 如果它返回true,则:
- 源指针是空的。 目标指针将被清除,以符合“将该指针的内容(无)移入该指针的语义请求”。
- 源指针指向的对象可以转换为目标指针types。 源指针将是空的,并且目标指针将指向它用来指向的同一个对象。 目标指针将接收源指针的删除器(仅在使用第一个重载时)。
- 如果返回false,则操作失败。 指针都不会改变状态。
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> >
的第二个。 删除器types将不兼容,因此编译器将不允许您使用此function。
这不是为什么的答案,但它是一种方法来做到这一点…
std::unique_ptr<A> x(new B); std::unique_ptr<B> y(dynamic_cast<B*>(x.get())); if(y) x.release();
它不是完全干净的,因为在短时间内, unique_ptr
认为它们拥有相同的对象。 正如所评论的那样,如果你使用一个删除器,你还必须pipe理一个自定义的删除器(但这非常less见)。
那么对于C ++ 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
pipe理的对象进行向下引用 :
auto derived = dynamic_cast<Derived&>(*pBase); derived.foo();