为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_ptrpipe理的对象进行向下引用

 auto derived = dynamic_cast<Derived&>(*pBase); derived.foo();