C++ unique_ptr 的动态转换

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11002641/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:44:03  来源:igfitidea点击:

Dynamic casting for unique_ptr

c++castingc++11smart-pointersunique-ptr

提问by betabandido

As it was the case in Boost, C++11 provides some functions for casting shared_ptr:

与 Boost 中的情况一样,C++11 提供了一些用于强制转换的函数shared_ptr

std::static_pointer_cast
std::dynamic_pointer_cast
std::const_pointer_cast

I am wondering, however, why there are no equivalents functions for unique_ptr.

但是,我想知道为什么没有unique_ptr.

Consider the following simple example:

考虑以下简单示例:

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));

Is there any reason why this usage pattern is discouraged, and thus, equivalent functions to the ones present in shared_ptrare not provided for unique_ptr?

有什么理由不鼓励这种使用模式,因此shared_ptr没有提供与 中存在的功能等效的功能unique_ptr吗?

采纳答案by Mark Ransom

The functions you refer to each make a copyof the pointer. Since you can't make a copy of a unique_ptrit doesn't make sense to provide those functions for it.

您引用的每个函数都会复制指针。由于您无法制作 a 的副本,unique_ptr因此为其提供这些功能是没有意义的。

回答by Jonathan Wakely

In addition to Mark Ransom's answer, a unique_ptr<X, D>might not even store an X*.

除了 Mark Ransom 的回答之外, aunique_ptr<X, D>甚至可能不会存储X*.

If the deleter defines the type D::pointerthen that's what is stored, and that might not be a real pointer, it only needs to meet the NullablePointerrequirements and (if unique_ptr<X,D>::get()is called) have an operator*that returns X&, but it isn't required to support casting to other types.

如果删除器定义了类型,D::pointer那么这就是存储的类型,并且可能不是真正的指针,它只需要满足NullablePointer要求并且(如果unique_ptr<X,D>::get()被调用)有一个operator*返回X&,但不需要支持转换为其他类型。

unique_ptris quite flexible and doesn't necessarily behave very much like a built-in pointer type.

unique_ptr非常灵活,不一定表现得非常像内置指针类型。

As requested, here is an example where the stored type is not a pointer, and therefore casting is not possible. It's a bit contrived, but wraps a made-up database API (defined as a C-style API) in a C++ RAII-style API. The OpaqueDbHandle type meets the NullablePointerrequirements, but only stores an integer, which is used as a key to lookup the actual DB connection via some implementation-defined mapping. I'm not showing this as an example of great design, just as an example of using unique_ptrto manage a non-copyable, movable resource which is not a dynamically-allocated pointer, where the "deleter" doesn't just call a destructor and deallocate memory when the unique_ptrgoes out of scope.

根据要求,这里有一个示例,其中存储的类型不是指针,因此无法进行转换。它有点做作,但在 C++ RAII 风格的 API 中包装了一个虚构的数据库 API(定义为 C 风格的 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);
}

回答by cdhowie

To build on Dave's answer, this template function will attempt to move the contents of one unique_ptrto another of a different type.

为了建立在 Dave 的回答之上,这个模板函数将尝试将一个的内容移动unique_ptr到另一个不同类型的内容。

  • If it returns true, then either:
    • The source pointer was empty. The destination pointer will be cleared to comply with the semantic request of "move the contents of this pointer (nothing) into that one."
    • The object pointed to by the source pointer was convertible to the destination pointer type. The source pointer will be empty, and the destination pointer will point to the same object it used to point to. The destination pointer will receive the source pointer's deleter (only when using the first overload).
  • If it returns false, the operation was unsuccessful. Neither pointer will have changed state.
  • 如果它返回 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;
}

Note that the second overload is required for pointers declared std::unique_ptr<A>and std::unique_ptr<B>. The first function will not work because the first pointer will actually be of type std::unique_ptr<A, default_delete<A> >and the second of std::unique_ptr<A, default_delete<B> >; the deleter types won't be compatible and so the compiler will not allow you to use this function.

请注意,声明std::unique_ptr<A>和的指针需要第二个重载std::unique_ptr<B>。第一个函数将不起作用,因为第一个指针实际上是类型std::unique_ptr<A, default_delete<A> >,第二个是std::unique_ptr<A, default_delete<B> >; 删除器类型将不兼容,因此编译器将不允许您使用此函数。

回答by David

This isn't an answer to why, but it is a way to do it...

这不是为什么的答案,但这是一种方法......

std::unique_ptr<A> x(new B);
std::unique_ptr<B> y(dynamic_cast<B*>(x.get()));
if(y)
    x.release();

It's not entirely clean since for a brief moment 2 unique_ptrs think they own the same object. And as was commented, you'll also have to manage moving a custom deleter if you use one (but that's very rare).

它并不完全干净,因为在短时间内 2unique_ptr秒认为他们拥有相同的对象。正如评论的那样,如果您使用自定义删除程序,您还必须管理移动自定义删除程序(但这种情况很少见)。

回答by Bob F

How about this for a C++11 approach:

对于 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);
}

回答by Emile Cormier

If you're only going the be using the downcast pointer in a small scope, one alternative is to simply downcast the referenceto the object being managed by the unique_ptr:

如果您只想在小范围内使用向下转换指针,一种替代方法是简单地向下转换对由 管理的对象的引用unique_ptr

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

回答by friedmud

I liked cdhowie's answer... but I wanted them to returninstead of using out-args. Here's what I came up with:

我喜欢 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;
}

I put it into a GitHub repo here: https://github.com/friedmud/unique_ptr_cast

我把它放到了一个 GitHub 仓库:https: //github.com/friedmud/unique_ptr_cast