Шаблонная функция или функция с указателем на базовый класс - PullRequest
0 голосов
/ 10 января 2020

Когда я хотел бы использовать функцию (здесь она называется do_some_work() / do_some_templated_work()) для нескольких различных типов входных классов, и я не хочу переписывать эту функцию несколько раз, насколько я знаю, я могу либо шаблон, который или извлекать все задействованные классы из базового класса и использовать указатель этого базового класса. Я написал короткую тестовую программу для обоих случаев:

#include <iostream>
#include <string>


class BaseClass {
public:
    BaseClass(){
        class_name = std::string("Base class");
    }

    virtual ~BaseClass(){};

    virtual double work_func(const double a, const double b) {
        (void) a;
        (void) b;
        return 0;
    };
    virtual void print_class_name(void) {};
private:
    std::string class_name;
};

class DerivedClassA : public BaseClass{
public:
    DerivedClassA(){
        class_name = std::string("Class A");
    }

    ~DerivedClassA() override {

    }

    double work_func(const double a, const double b) override{
        return a + b;
    }

    void print_class_name(void) override{
        std::cout << class_name << '\n';
    }

private:
    std::string class_name;
};

class DerivedClassB : public BaseClass{
public:
    DerivedClassB(){
        class_name = std::string("Class B");
    }

    ~DerivedClassB() override {

    }

    double work_func(const double a, const double b) override{
        return a - b;
    }

    void print_class_name(void) override{
        std::cout << class_name << '\n';
    }
private:
    std::string class_name;
};

void do_some_work(BaseClass &test_class){
    test_class.print_class_name();
    std::cout << test_class.work_func(5, 6) << '\n';
}

template <class T>
void do_some_templated_work(T &test_class) {
    test_class.print_class_name();
    std::cout << test_class.work_func(5, 6) << '\n';
}

int main()
{
    std::cout << "Hello World!" << std::endl;
    DerivedClassA AClass;
    DerivedClassB BClass;
    do_some_work(AClass);
    do_some_work(BClass);
    do_some_templated_work(AClass);
    do_some_templated_work(BClass);
    return 0;
}

Когда я смотрел на код ASM, я не увидел ни одного непосредственного преимущества (хотя это может быть связано с переключателями компиляции). Следовательно, есть ли какие-то вещи, которые я здесь не рассматривал, и имеют ли оба подхода свои преимущества / недостатки при сравнении обоих? Или есть третий подход, который я мог бы использовать для той же цели?

1 Ответ

1 голос
/ 10 января 2020

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

То, что вы не могли сделать с наследованием из коробки, например, возвращает значения различных типов из work_func - это - то, где шаблоны сияют.

С другой стороны, наследование (особенно при следовании принципу подстановки Лискова) может сделать контракт / ожидания интерфейса намного более ясным (void do_some_templated_work(T &test_class) не говорит вам, что T необходимо реализовать, например, print_class_name).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...