【问题标题】:Templated function or function with pointer to base class模板化函数或带有指向基类的指针的函数
【发布时间】:2020-01-10 16:37:12
【问题描述】:

当我想为几种不同类型的输入类使用一个函数(这里称为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 代码时,我没有看到其中任何一个的直接优势(不过,这可能与编译开关有关)。因此,这里有什么我没有考虑的事情吗?在比较两者时,这两种方法是否各有优缺点?或者是否有第三种方法可以用于相同目的?

【问题讨论】:

  • 你知道纯虚函数吗?如果你写= 0;而不是BaseClass中那些虚函数的无意义的函数体,它就变成了一个抽象基类。如果您不熟悉它,我建议您研究一下。
  • 此外,如果您只是通过重新声明将class_name 隐藏在所有派生类中,那么在基类中声明class_name 是没有意义的。成员变量不能被覆盖或类似的东西。
  • “在查看 ASM 代码时,我没有看到其中任何一个的直接优势” 对于那个玩具示例,编译器可能会进行非常好的优化(去虚拟化),使上述选择相同.如果它不做这些优化,你的静态分派(模板函数)仍然对运行时分派有作用,因为来自T 的调用方法不是finalT 不一定是最派生的类型)。

标签: c++ class templates


【解决方案1】:

一般来说,第一个选项涉及虚拟调度(即在运行时跳转到正确的函数),而第二个选项已经知道要在编译时调用的正确函数。后者通常具有较少的开销并为编译器开辟了更多优化机会,但可能存在缺点(代码大小、指令缓存等)。性能将始终取决于细节,所以如果你关心它,请介绍一下。

开箱即用的继承不能做的事情是,例如从 work_func 返回不同类型的值 - 这就是模板的亮点。

另一方面,继承(尤其是在遵循 Liskov 替换原则时)可以使接口的契约/期望更加清晰(void do_some_templated_work(T &amp;test_class) 并没有告诉您 T 需要实现,例如 print_class_name) .

【讨论】:

  • 参考。性能:从一个概念更改为另一个概念相当耗时,并且需要进行大量代码更改,因此如果我不必为这两种情况分析完整代码,我会更喜欢
猜你喜欢
  • 2014-04-14
  • 1970-01-01
  • 2010-09-13
  • 1970-01-01
  • 2015-04-26
  • 2016-10-18
  • 2015-01-20
  • 2011-12-09
相关资源
最近更新 更多