Подтвердить что ты не робот

Динамическое литье для unique_ptr

Как и в случае Boost, С++ 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
4b9b3361

Ответ 1

Функции, к которым вы обращаетесь, делают копию указателя. Поскольку вы не можете сделать копию unique_ptr, нет смысла предоставлять эти функции для нее.

Ответ 2

В дополнение к Mark Ransom answer, unique_ptr<X, D> может даже не хранить X*.

Если делетер определяет тип D::pointer, то то, что хранится и которое может не быть реальным указателем, оно должно удовлетворять требованиям NullablePointer и (если unique_ptr<X,D>::get() вызывается) имеет operator*, который возвращает X&, но не требуется поддерживать листинг для других типов.

unique_ptr довольно гибкий и не обязательно ведет себя очень похоже на встроенный тип указателя.

В соответствии с запросом, вот пример, где хранимый тип не является указателем, и поэтому литье невозможно. Это немного надуманно, но завершает API созданной базы данных (определенный как API-интерфейс C) в API-интерфейсе С++ RAII. Тип OpaqueDbHandle удовлетворяет требованиям NullablePointer, но только сохраняет целое число, которое используется как ключ для поиска фактического соединения с БД посредством определенного отображения, определенного реализацией. Я не показываю это как пример великолепного дизайна, как пример использования unique_ptr для управления не скопированным подвижным ресурсом, который не является динамически выделенным указателем, где "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);
}

Ответ 3

Чтобы построить ответ на Дэйв, эта функция шаблона попытается переместить содержимое одного 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> >; типы дебетеров не будут совместимы, поэтому компилятор не позволит вам использовать эту функцию.

Ответ 4

Это не ответ на вопрос, почему, но это способ сделать это...

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

Это не совсем чисто, так как на короткое время 2 unique_ptr считают, что у них есть один и тот же объект. И, как было прокомментировано, вам также придется управлять перемещением пользовательского делетера, если вы используете один (но это очень редко).

Ответ 5

Как насчет этого для подхода С++ 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);
}

Ответ 6

Если вы только собираетесь использовать указатель понижения в малой области видимости, одна альтернатива заключается в том, чтобы просто сбрасывать ссылку на объект, управляемый с помощью unique_ptr:

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