unique_ptr的动态转换

bet*_*ido 49 c++ casting smart-pointers unique-ptr c++11

正如Boost中的情况一样,C++ 11提供了一些用于转换的函数shared_ptr:

std::static_pointer_cast
std::dynamic_pointer_cast
std::const_pointer_cast
Run Code Online (Sandbox Code Playgroud)

但是,我想知道为什么没有等效功能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));
Run Code Online (Sandbox Code Playgroud)

是否有任何理由不鼓励这种使用模式,因此,shared_ptr没有提供与现有模式相同的功能unique_ptr

Jon*_*ely 38

除了Mark Ransom的答案之外,unique_ptr<X, D>甚至可能没有存储X*.

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

unique_ptr 非常灵活,并不一定非常像内置指针类型.

根据要求,这里是存储类型不是指针的示例,因此无法进行转换.它有点人为,但在C++ RAII风格的API中包装了一个虚构的数据库API(定义为C风格的API).OpaqueDbHandle类型符合NullablePointer要求,但仅存储一个整数,该整数用作通过某些实现定义的映射查找实际DB连接的键.我没有将此作为优秀设计的示例,仅作为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);
}
Run Code Online (Sandbox Code Playgroud)

  • +1我从没想过使用`unique_ptr`为数据库实现RAII方法:)谢谢你的例子. (3认同)

Mar*_*som 32

您引用的函数都会生成指针的副本.既然你不能复制unique_ptr它,那么为它提供这些功能是没有意义的.

  • 这是事实,但如果唯一的目的是移动指针呢?在这种情况下,`unique_ptr`的强制转换函数不会复制,只是移动(或转换)指针. (17认同)

cdh*_*wie 12

为了构建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;
}
Run Code Online (Sandbox Code Playgroud)

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


Dav*_*vid 5

这不是一个答案,为什么,但它是一个办法做到这一点?

std::unique_ptr<A> x(new B);
std::unique_ptr<B> y(dynamic_cast<B*>(x.get()));
if(y)
    x.release();
Run Code Online (Sandbox Code Playgroud)

它并不完全干净,因为在短暂的时刻,我们unique_ptr认为它们拥有相同的物体.正如评论所说,如果您使用自定义删除器,您还必须管理移动自定义删除器(但这非常罕见).

  • 如果你有一个`unique_ptr <A,Deleter>`,你需要移动删除器,这会更复杂. (6认同)