2016-08-15 5 views
1

У меня есть базовый класс, который предоставляет чистые виртуальные интерфейсы. Мне нужно это для хранения указателей на объекты производного класса в списке указателей на базовый класс.Абстрактный базовый класс с использованием аргумента шаблона из производного класса

Производный класс создается с использованием механизма шаблона. Проблема в том, что если я хочу, чтобы виртуальный интерфейс возвращал тип, который известен только производному классу, мне нужно передать его как аргумент шаблона. Это где начинается дилемма ...

template <typename ITEM> 
class base { 
public: 
virtual ITEM* get() = 0; 
}; 

template <typename ITEM> 
class derived : public base<ITEM>{ 
public: 
ITEM* get() {...}; 
}; 

Но при использовании шаблона в базе мне нужно знать это, даже при создании списка базовых указателей:

base* myList[10] = {derived1, derived2,...} 

я, конечно, не знаете этот тип, когда я определяю свой список. Поэтому мне нужно как-то избавиться от шаблона в моем базовом классе.

РЕДАКТИРОВАТЬ: Избавился от этого подхода, потому что это был не очень полезный подход. Поэтому нет решения для этой проблемы.

+8

Предположим, вам каким-то образом удалось установить 'myList'. Как именно вы планируете его использовать? Вы могли бы назвать, скажем, 'myList [5] .get()' - но что бы вы сделали с результатом, когда вы не знаете, какой он тип? –

+0

Возможно, вы можете проверить 'dynamic_cast', я не знаю, будет ли он работать с шаблоном, хотя – Ceros

+0

Это может быть то, что у вас есть, но вы спрашиваете о ** его ** [по неправильным причинам] (http: /meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – StoryTeller

ответ

1

Код, который вы пишете, недействителен; не существует ни одного типа base, который затем параметризуется, как в Java, но существует ряд типов base<T>. Существует способ получить оболочку для действительно общего объекта, и он называется стиранием типа. Он используется, например, при реализации boost :: any.

В принципе, у вас есть базовый класс без шаблонов с виртуальными функциями, а затем вы создаете производный класс шаблона, который их реализует. Обратите внимание, что версия упрощена показано здесь делает не работы, если вы хотите иметь массив base объектов, потому что base имеет чисто виртуальные функции и, следовательно, не может быть создан (и потому что элемент T производного типа будет отрезанный).

struct base; 
template<typename T> 
struct derived; 

struct base { 
    virtual ~base(); 

    // In this class we don't know about T, so we cannot use it 
    // Other operations that delegate to the derived class are possible, though 
    virtual std::size_t sizeofT() const = 0; 
    virtual const std::type_info& typeofT() const = 0; 

    // Since all you want is a pointer in "get", you could write it as a void* 
    virtual void* getPtr() = 0; 

    // Otherwise, we can implement this template function here that calls the virtual. 
    // Note that function templates cannot be virtual! 
    template<typename U> 
    U& getAs() { 
     // Verify that the type is the _same_ (no up/downcasts allowed) 
     // std::bad_cast is thrown here if U is not the same T used to build this object 
     derived<U>& meAsU = dynamic_cast<derived<U>&>(*this); 
     return meAsU.obj; 
    } 
}; 

template<typename T> 
struct derived : public base { 
    T obj; 
    // A couple of ctors to initialize the object, and the default copy/move ctors/op= 
    virtual ~derived(); 
    derived(const T& o) : obj(o) {} 
    derived(T&& o) : obj(std::move(o)) {} 

    std::size_t sizeofT() const override { 
     return sizeof(T); 
    } 
    const std::type_info& typeofT() const override { 
     return typeid(T); 
    } 
    void* getPtr() override { 
     return static_cast<void*>(&obj); 
    } 
}; 

Если вы хотите использовать base типа непосредственно в качестве переменной, либо в массиве или контейнер (вектор, список и т.д.), вам необходимо динамическое распределение - нет два пути вокруг него. У вас есть два варианта, которые отличаются от того, где возложить ответственность за динамическое распределение:

  • Вы можете использовать раствор выше, если вы ограничиваете себя, имеющие массивы указателей к base. Например. массив из std::unique_ptr<base>. Объекты с заостренными объектами будут иметь тип derived<something>.

    base err1; // Error, abstract class (what would it contain?) 
    base err2 = derived<int>(2); // Still abstract class, and the int would be sliced off 
    std::unique_ptr<base> ok(new derived<int>(3)); // Works 
    
    std::vector<std::unique_ptr<base>> objects; 
    objects.push_back(std::make_unique(new derived<int>(5))); 
    objects.push_back(std::make_unique(new derived<std::string>(2))); 
    int& a = objects[0].getAs<int>(); // works 
    std::string& b = objects[1].getAs<std::string>(); // works too 
    std::string& bad = objects[1].getAs<double>(); // exception thrown 
    
  • В противном случае вам придется реализовать динамическое распределение в самих базовых классах. Это классы, такие как boost :: any или std :: function do. Простейший объект any просто был бы оберткой уникального-ptr класса, который я показал здесь, с соответствующими реализациями операторов и т. Д. Затем у вас может быть переменная типа any x = y;, и класс будет внутри своего конструктора делать требуется требуемое значение new derived<Y>(y).

+0

Это не то, как работает 'dynamic_cast'. –

+0

@ T.C. черт возьми, правда, я забыл, что dynamic_cast можно использовать _towards_ void *, но не _from_ it. Ну, я удалю часть getAs, так как принцип «стирания типа» работает без него. –

+0

в любом случае, спасибо за этот хороший маленький фрагмент. Мне понравилось читать его, хотя я принял еще один подход к решению моей проблемы! – binaryguy

Смежные вопросы