2016-03-01 6 views
-1

Я хотел бы сохранить дочерний объект в контейнере его родительского типа, а затем вызвать функцию перегрузки в зависимости от типа дочернего элемента в контейнере. Это возможно?Функция перегрузки вызова на основе дочернего элемента в родительском контейнере

#include <vector> 

class Parent { public: }; 

class A : public Parent { public: }; 
class B : public Parent { public: }; 
class C : public Parent { public: }; 

class Hander 
{ 
public: 
    static void handle(A & a) {} 
    static void handle(B & b) {} 
    static void handle(C & c) {} 
}; 

int main() 
{ 
    A test1; 
    Hander::handle(test1); // compiles and calls the correct overload 

    Parent test2 = A(); 
    Hander::handle(test2); // doesn't compile 

    Parent * test3 = new A(); 
    Hander::handle(*test3); // doesn't compile 

    Parent children1[] = { A(), B(), C() }; 
    for (int i = 0; i < 3; ++i) 
     Hander::handle(children1[i]); // doesn't compile 

    std::vector<Parent*> children2 = { new A(), new B(), new C() }; 

    for (int i = 0; i < 3; ++i) 
     Hander::handle(*children2[i]); // doesn't compile 
} 
+4

прочитайте о полиморфизме и разрезе объектов. – NathanOliver

+2

** Сначала: ** Я думаю, что Parent test2 = A() 'не делает то, что вы думаете. В * C++ * существует полиморфизм только для указателей и ссылок. 'test2' имеет тип' Parent' и построен из 'A', но он больше не имеет типа' A'. ** Второе: ** Перегруженные функции выбираются из компилятора. Если вы хотите вызвать нужную функцию, вам нужно вручную проверить, какой тип она есть. Но вы можете заинтересоваться [* виртуальными функциями *] (http://stackoverflow.com/q/2391679/4967497). С помощью виртуальной функции вы можете сделать 'handle' членом' A', 'B' и' C', и он всегда будет вызывать правильную функцию автоматически. – JojOatXGME

+0

@JojOatXGME Мне известно о возможности проверки вручную типа (с использованием интеллектуального указателя), но у меня было впечатление, что это анти-шаблон. Это верно? –

ответ

1

Нет, это невозможно.

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

Base &o = getSomeObject(); 
handle(o); 

Компилятор не знает тип O реального. Он знает только, что это какой-то подтип Base или Base. Это означает, что он будет искать функцию, которая захватывает объекты типа Base.

Вы могли бы реализовать проверку типа самостоятельно или использовать карту для хранения возможных функций:

Base &o = getSomeObject(); 
functionMap[typeid(o)](o); 

Но typeid работает только этот whay если Base является полиморфным типом. Это означает, что у него должна быть хотя бы одна виртуальная функция . Это приводит нас к следующему разделу:

Но вы можете использовать виртуальные функции.

Virtual functionsнестатические функции-члены классов, которые могут быть переопределены. Правильная функция разрешена во время выполнения. Следующий код будет выходной Subt вместо Base:

class Base { 
public: virtual std::string f() {return "Base"} 
}; 
class Subt : public Base { 
public: virtual std::string f() {return "Subt"} 
}; 

int main() { 
    Subt s; 
    Base &b = s; 
    std::cout << b.f() << std::endl; 
} 

Вы можете опустить virtual в определении Subt. Функция f() уже определена как virtual в базовом классе.

Классы с по меньшей мере одной виртуальной функции (также называемые полиморфные типы) хранят ссылку на таблицу в виртуальной функции (также называемые виртуальные таблицы). Эта таблица используется для получения правильной функции во время выполнения.

Проблема в вашем вопросе может быть решена так:

class Parent { 
public: 
    virtual void handle() = 0; 
}; 

class A : public Parent { 
public: 
    void handle() override { /* do something for instances of A */ } 
}; 
class B : public Parent { 
public: 
    void handle() override { /* do something for instances of B */ } 
}; 
class C : public Parent { 
public: 
    void handle() override { /* do something for instances of C */ } 
}; 

int main() 
{ 
    std::vector<std::unique_ptr<Parent>> children = { 
      std::make_unique<A>(), 
      std::make_unique<B>(), 
      std::make_unique<C>()}; 

    for (const auto &child : children) 
     child->handle(); 
} 

Примечание о совместимости: Ключевые слова auto и override доступны только в C++ 11 и выше. range-based for loop и std::unique_ptr также доступны с C++ 11. Функция std::make_unique доступна с C++ 14. Но виртуальная функция также может использоваться со старыми версиями.

Другой намек:

полиморфизм работает только со ссылками и указателями. Следующий бы назвал Base::f() и не Subt::f():

Subt s; 
Base b = s; 
std::cout << b.f() << std::endl; 

В этом примере b будет содержать только объект типа Base вместо Subt. Объект создается только по адресу Base b = s;. Он может скопировать некоторую информацию от s, но это не s. Это новый объект типа Base.

+0

Большое спасибо за подробный ответ! Это объясняет все, что я ищу, и многое другое. И да, я всегда использую управляемые указатели, если только я не даю пример с восьмью костями для SO. –

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