C ++ приведение к «дяде класса» - PullRequest
0 голосов
/ 26 октября 2018

У меня есть Wrapper<T> шаблонный класс.

У меня есть базовый класс A.

У меня есть производный класс B: общедоступный A.

Что я хочудолжен хранить Wrapper<B> в vector<Wrapper<A>>.

Я знаю, что это технически неправильно, поскольку Wrapper<B> не является прямым подклассом Wrapper<A>

См. схему наследования

Но каким-то образом STL допускает это для shared_ptr:

#include <memory>
#include <vector>
#include <iostream>

struct A
{
  A() {}
  virtual ~A() {}
  virtual void print() { std::cout << "A" << std::endl; }
};

struct B : public A
{
  B() {}
  virtual ~B() {}
  virtual void print() { std::cout << "B" << std::endl; }
};

int main()
{
  std::vector<std::shared_ptr<A>> v;

  v.push_back(std::make_shared<A>());
  v.push_back(std::make_shared<B>());

  for (auto &e : v)
    e->print();

  return 0;
}

Как я могу добиться того же результата для моего Wrapper<T> класса?

Этонекомпилируемый код:

#include <vector>
#include <iostream>

template <class T>
struct W
{
};

struct A
{
  A() {}
  virtual void print() { std::cout << "A" << std::endl; }
};

struct B : public A
{
  B() {}
  virtual void print() { std::cout << "B" << std::endl; }
};

int main()
{
  std::vector<W<A>> v;

  v.push_back(W<A>());
  v.push_back(W<B>());

  for (auto &e : v)
    e->print();

  return 0;
}

Бест, Пьер

1 Ответ

0 голосов
/ 26 октября 2018

Общие ptr для A и B не связаны между собой.

Что делает общий ptr, так это предоставляет конвертирующий конструктор. Пока типы указателей могут быть преобразованы, он принимает как raw-pointer-to, так и shared-ptr-to.

Shared ptr type стирает способ окончательного уничтожения и сохраняет указатель, так что это действительно не сложно.

Как именно это будет работать, будет зависеть от того, как хранятся данные и что делает Wrapper.

Но:

template<class T>
struct Wrapper {
  template<class U>
  Wrapper(Wrapper<U>);
};

в основном это. Может быть, какой-нибудь SFINAE, чтобы получить более ранние ошибки.

...

Поскольку вы действительно добавили детали в комментарии, у вас есть:

template<class T>
struct RollbackElement : public T

как Wrapper. Ответ: нет, вы не можете этого сделать.

Вы можете сделать:

template<class T>
struct RollbackElement : public std::any {
  operator T&() {
    return std::any_cast<T&>(*this);
  }
};

, где вместо хранения фактического T вы храните обертку для стирания типа вокруг T.

template<class T>
struct RollbackElement : private std::any {
  operator T&() {
    return *get();
  }
  operator T const&() const {
    return *get();
  }
  RollbackElement( T in ):
    std::any(std::move(in)),
    get_impl([](std::any& x)->T*{return std::any_cast<T*>(&x);})
  {}
  RollbackElement():RollbackElement({}) {}
  template<class U>
  RollbackElement( RollbackElement<U> const& o ):
    std::any( static_cast<std::any const&>(o) ),
    get_impl(o.get_impl)
  {}
  // note: due to quirks in `std::any`, a move ctor isn't safe
  T* operator->() { return get(); }
  T const* operator->() const { return get(); }
  T* get() { return get_impl(*this); }
  T const* get() const { return get_impl(const_cast<std::any&>(*this)); }
private:
  std::function<T*(std::any&)> get_impl = {};
};

плюс любое увеличение на нем.

Хитрость в том, что ваше хранилище, std::any, может хранить что угодно.

Мы храним, как добраться от std::any до T* в get_impl.

Когда мы копируем RollbackElement, мы копируем оба any (без приведения к конкретному типу) и get_impl. Если другой откат имеет тип другого , мы полагаемся на конструктор преобразования std::function, чтобы скорректировать возвращаемые указатели для нас.

...