54

与 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

4

9 回答 9

40

除了 Mark Ransom 的答案之外, aunique_ptr<X, D>甚至可能不存储 a 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);
}
于 2012-06-12T18:51:26.330 回答
34

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

于 2012-06-12T18:40:03.097 回答
14

为了建立戴夫的答案,这个模板函数将尝试将一个内容移动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> >; 删除器类型将不兼容,因此编译器将不允许您使用此函数。

于 2012-06-12T19:01:57.503 回答
6

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

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

它并不完全干净,因为有一小会 2unique_ptr秒认为他们拥有同一个对象。正如所评论的那样,如果您使用自定义删除器,您还必须管理移动自定义删除器(但这非常罕见)。

于 2012-06-12T18:41:56.297 回答
4

对于 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);
}
于 2013-02-08T16:49:15.030 回答
3

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

auto derived = dynamic_cast<Derived&>(*pBase);
derived.foo();
于 2016-08-15T01:35:05.980 回答
1

我改进了@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>()); 
于 2021-01-27T17:35:52.323 回答
0

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

于 2018-10-10T23:04:27.127 回答
0

到目前为止我看到的所有样本都想出了这个版本。

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());
于 2020-07-01T20:46:30.600 回答