Как вывести тип производного класса

Допустим, у меня есть базовый абстрактный класс:

class Base  
{
 public:
 virtual ~Base(){};
 };

и производный от него класс шаблона:

template<typename T>
class Derived : public Base 
{
 public:
    void function(); 
};

И еще один класс Test, который имеет вектор объектов базового класса

 class Test
 {
 public:
 vector< Base* > base_vector;
 };

ВОПРОС 1: Если у нас есть вектор указателей на объекты класса Base, то их тоже можно заполнить указателями на классы Derived, верно?

Итак, скажем, теперь я обращаюсь к этому вектору из уже существующего тестового класса, который ранее был заполнен указателями на объекты типа класса Derived.

void (Test test)
{
    vector<Base*> objects = test.base_vector;
}

ВОПРОС 2: Можно ли вывести имя типа производных объектов, на которые указывают указатели? (Производный класс является шаблоном)

ИЛИ Можно ли вызвать функцию(), которая была определена в производном классе, и не заботиться о типе производного класса?

Если нет, то какие минимальные изменения необходимо внести в любой из этих классов?


person Alex Shirokov    schedule 25.10.2016    source источник
comment
Не могли бы вы объяснить немного больше, в чем ваша проблема? Is there a way to deduce type of Derived classes не имеет смысла.   -  person Qix - MONICA WAS MISTREATED    schedule 25.10.2016
comment
Очевидно, что этот класс также принимает производные классы... вам лучше прочитать о нарезке объектов, потому что размещение Derived в векторе Base может не дать ожидаемого эффекта.   -  person 463035818_is_not_a_number    schedule 25.10.2016
comment
Чтобы иметь полиморфный вектор (с объектами производных классов), он должен содержать указатели, а не объекты.   -  person Jacques de Hooge    schedule 25.10.2016
comment
Сделайте какой-нибудь пример с чем-то, что лучше понять, например, class Dog : public Animal или class Boat : public TransportationDevice. Затем опишите, чего вы хотите достичь, например, я хочу построить вектор‹Животное*›, который собирает всех животных в зоопарке, а затем собирает необходимую им пищу. Дело в том, что нам нужно увидеть ваш дизайн. Не должно случаться слишком часто, что вам нужно знать точный класс чего-либо, если вы работаете над его базовым классом. Я предполагаю, что вы несколько новичок в этой концепции.   -  person Aziuth    schedule 27.10.2016
comment
Тем не менее, есть две основные вещи для вас: 1.: имена переменных или классов - это то, что вы используете как программист, но это не используется компьютером. Как только вы скомпилируете, они исчезнут. 2.: Вы всегда можете добавить метод, который возвращает что-то, связанное с типом. Подобно виртуальному статическому типу животных get_animal_type() const; в базовом классе Animal, где AnimalType представляет собой какое-то перечисление, строку или какой-либо другой способ хранения типа, и каждый подкласс перезаписывает этот метод чем-то вроде AnimalType Dog::get_animal_type() const { return dog; }.   -  person Aziuth    schedule 27.10.2016


Ответы (2)


Если я правильно понимаю вашу проблему, вам нужно использовать виртуальные функции. Если вы хотите вызвать функцию для объектов и убедиться, что вызывается функция правильного типа, сделайте свой function виртуальным. Поэтому вам нужно объявить его в своем классе Base. Кроме того, вам нужно, чтобы ваш вектор был вектором указателей на Base. Затем, когда вы вызываете свой function для объектов вектора, он вызывает функцию из производного класса, если объекты действительно относятся к производному типу.

Таким образом, вам не нужно выяснять тип объектов, вы просто вызовете виртуальную функцию, и она обнаружит, что это за тип объекта, и вызовет правильную функцию.

person grigor    schedule 25.10.2016
comment
Но что, если я не могу внести какие-либо изменения в базовый класс? - person Alex Shirokov; 26.10.2016

Я не уверен, что понял, что вы хотите, но будет ли эта модификация вашего кода работать для вас? Таким образом, вам либо вообще не нужно выводить тип, либо вы можете получить виртуальный метод type(), который вернет вам перечисление с некоторым уникальным значением для каждого типа.

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

class Base {
public:
  virtual void function() = 0;
  virtual ~Base() = default;
};

template <class T> class Derived : public Base {
public:
  void function() override {
      // to produce some output
      std::cout << typeid(T).name() << std::endl; 
  }
};

class Test {
public:
  std::vector<std::unique_ptr<Base>> objects;
};

int main() {
  Test test;
  test.objects.push_back(std::make_unique<Derived<int>>());
  test.objects.push_back(std::make_unique<Derived<std::string>>());
  for (auto &ptr : test.objects) {
      ptr->function();
  }
}

Альтернативным решением этой проблемы является использование класса variant, например boost ::variant или std::experimental::variant (если он уже есть в вашей стандартной библиотеке). Таким образом, вам не нужно иметь virtual function() в базовом классе. boost::variant имеет метод which(), который возвращает вас и целое число типа в списке типов. Пример:

#include <boost/variant.hpp>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Base {
public:
  virtual ~Base() = default;
};

template <class T> class Derived : public Base {
public:
  void function() {
    std::cout << typeid(T).name() << std::endl;
  }
};

class Test {
public:
  std::vector<boost::variant<Derived<int>, Derived<std::string>>> objects;
};

int main() {
  Test test;
  test.objects.push_back(Derived<int>());
  test.objects.push_back(Derived<std::string>());
  for (auto& obj : test.objects) {
    switch (obj.which()) {
      case 0:
        boost::get<Derived<int>>(obj).function();
        break;
      case 1:
        boost::get<Derived<std::string>>(obj).function();
        break;
    }
  }
  return 0;
}
person Pavel Davydov    schedule 25.10.2016
comment
Первое решение очень законно! Однако что, если класс Base настолько существенен, что вы просто не можете внести в него какие-либо изменения? - person Alex Shirokov; 26.10.2016
comment
@AlexShirokov, вы можете просто попытаться преобразовать его в Derived‹T› с помощью dynamic_cast: if (auto derived = dynamic_cast<Derived<int>>(ptr.get())) { ptr->function(); }. Он вернет nullptr, если объект имеет несовместимый тип (т.е. не Derived‹int› или производный от него). Однако обратите внимание, что такой код скоро станет беспорядочным, гораздо чище просто добавить функцию в базовый класс. - person Pavel Davydov; 26.10.2016
comment
@AlexShirokov Если кто-то где-то использует указатель на базовый класс, то этим он заявляет, что хочет именно то, что предоставляет интерфейс базовых классов. Если он хочет что-то вдобавок, например, функцию, возвращающую тип, то базовый класс — это не то, что он хочет использовать. Использование трюков, чтобы обойти этот запах кода. Что можно было бы сделать, так это создать класс, который находится между ними и предоставляет правильный интерфейс, из которого затем получаются подклассы и тип указателя. Конечно, в любом случае такие вещи должны решаться в каком-то реальном контексте. - person Aziuth; 27.10.2016