就像在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));
有什么理由不鼓励这种使用模式,因此没有为unique_ptr
提供与shared_ptr
中存在的功能的等效功能?
除了马克·兰塞姆的回答,unique_ptr<X, D>
甚至可能不存储X*
。
如果删除器定义了类型D::pointer
那么这就是存储的内容,这可能不是一个真正的指针,它只需要满足NullablePointer
要求,并且(如果调用unique_ptr<X,D>::get()
)有一个返回X&
的operator*
,但它不需要支持转换为其他类型。
unique_ptr
非常灵活,其行为不一定很像内置指针类型。
根据要求,下面是一个示例,其中存储的类型不是指针,因此无法进行强制转换。它有点做作,但将一个虚构的数据库 API(定义为 C 样式 API)包装在一个C++ RAII 风格的 API 中。 OpaqueDbHandle 类型满足NullablePointer
要求,但仅存储一个整数,该整数用作通过某些实现定义的映射查找实际数据库连接的键。 我并不是将其作为伟大设计的示例,只是作为使用 unique_ptr
来管理不可复制、可移动资源的示例,该资源不是动态分配的指针,其中"删除器"不只是调用析构函数并在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
因此为其提供这些功能是没有意义的。
为了构建 Dave 的答案,此模板函数将尝试将一个unique_ptr
的内容移动到另一个不同类型的内容。
- 如果它返回 true,则:
- 源指针为空。 目标指针将被清除,以符合"将此指针的内容(无)移动到该指针中"的语义请求。
- 源指针指向的对象可转换为目标指针类型。 源指针将为空,目标指针将指向它以前指向的同一对象。 目标指针将接收源指针的删除器(仅在使用第一个重载时)。
- 如果返回 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> >
类型;删除程序类型将不兼容,因此编译器将不允许您使用此函数。
这不是为什么的答案,但这是一种方法......
std::unique_ptr<A> x(new B);
std::unique_ptr<B> y(dynamic_cast<B*>(x.get()));
if(y)
x.release();
它并不完全干净,因为在短暂的时刻,2 unique_ptr
认为他们拥有相同的对象。正如评论的那样,如果您使用自定义删除器,您还必须管理移动自定义删除器(但这非常罕见)。
如果您只打算在较小的范围内使用向下转换指针,一种替代方法是简单地向下转换对unique_ptr
管理的对象的引用:
auto derived = dynamic_cast<Derived&>(*pBase);
derived.foo();
对于 C++11 方法来说,这怎么样:
template <class T_SRC, class T_DEST>
inline 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>(dest_ptr);
}
我改进了@Bob F https://stackoverflow.com/a/14777419/1702991 的答案,因此您现在只需要一个模板参数,就像往常一样,对于其他类型的演员表
template <class destinationT, typename sourceT>
std::unique_ptr<destinationT> unique_cast(std::unique_ptr<sourceT>&& source)
{
if (!source)
return std::unique_ptr<destinationT>();
// Throws a std::bad_cast() if this doesn't work out
destinationT* dest_ptr = &dynamic_cast<destinationT&>(*source.get());
source.release();
return std::unique_ptr<destinationT>(dest_ptr);
}
更新(非抛出版本):
template <class destinationT, typename sourceT>
std::unique_ptr<destinationT> unique_cast(std::unique_ptr<sourceT>&& source)
{
if (!source)
return std::unique_ptr<destinationT>();
destinationT* dest_ptr = dynamic_cast<destinationT*>(source.get());
if(dest_ptr)
source.release();
return std::unique_ptr<destinationT>(dest_ptr);
}
用法:
std::unique_ptr<MyClass> obj = unique_cast<MyClass>(std::make_unique<MyOtherClass>());
我喜欢cdhowie的回答...但我希望他们返回而不是使用 out-args。 这是我想到的:
template <typename T_DEST, typename T_SRC, typename T_DELETER>
std::unique_ptr<T_DEST, T_DELETER>
dynamic_pointer_cast(std::unique_ptr<T_SRC, T_DELETER> & src)
{
if (!src)
return std::unique_ptr<T_DEST, T_DELETER>(nullptr);
T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
if (!dest_ptr)
return std::unique_ptr<T_DEST, T_DELETER>(nullptr);
std::unique_ptr<T_DEST, T_DELETER> dest_temp(dest_ptr, std::move(src.get_deleter()));
src.release();
return dest_temp;
}
template <typename T_SRC, typename T_DEST>
std::unique_ptr<T_DEST>
dynamic_pointer_cast(std::unique_ptr<T_SRC> & src)
{
if (!src)
return std::unique_ptr<T_DEST>(nullptr);
T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
if (!dest_ptr)
return std::unique_ptr<T_DEST>(nullptr);
std::unique_ptr<T_DEST> dest_temp(dest_ptr);
src.release();
return dest_temp;
}
我把它放到GitHub的存储库中:https://github.com/friedmud/unique_ptr_cast
我看到的所有示例都提出了这个版本。
template <typename T_DEST, typename T_SRC, typename T_DELETER>
std::unique_ptr<T_DEST, T_DELETER>
dynamic_pointer_cast(std::unique_ptr<T_SRC, T_DELETER>&& src)
{
// When nullptr, just return nullptr
if (!src) return std::unique_ptr<T_DEST, T_DELETER>(nullptr);
// Perform dynamic_cast, throws std::bad_cast() if this doesn't work out
T_DEST* dest_ptr = dynamic_cast<T_DEST*>(src.get());
// Do not return nullptr on bad_cast
//if (!dest_ptr) return std::unique_ptr<T_DEST, T_DELETER>(nullptr);
// But throw std::bad_cast instead
if (!dest_ptr) throw std::bad_cast();
// Move into new unique_ptr
std::unique_ptr<T_DEST, T_DELETER> dest_temp(dest_ptr, std::move(src.get_deleter()));
src.release();
return dest_temp;
}
template <typename T_DEST, typename T_SRC>
std::unique_ptr<T_DEST>
dynamic_pointer_cast(std::unique_ptr<T_SRC>&& src)
{
// When nullptr, just return nullptr
if (!src) return std::unique_ptr<T_DEST>(nullptr);
// Perform dynamic_cast, throws std::bad_cast() if this doesn't work out
T_DEST* dest_ptr = dynamic_cast<T_DEST*>(src.get());
// Do not return nullptr on bad_cast
//if (!dest_ptr) return std::unique_ptr<T_DEST>(nullptr);
// But throw std::bad_cast instead
if (!dest_ptr) throw std::bad_cast();
// Move into new unique_ptr
std::unique_ptr<T_DEST> dest_temp(dest_ptr);
src.release();
return dest_temp;
}
如果你想让dynamic_cast抛出std::bad_cast,只需dynamic_cast
引用即可像这样使用它
auto src = std::make_unique<Base>();
auto dst = dynamic_pointer_cast<Derived>(std::move(src));
auto dst2 = dynamic_pointer_cast<Derived>(FunctionReturningBase());