模板化函数或带有指向基类的指针的函数
Posted
技术标签:
【中文标题】模板化函数或带有指向基类的指针的函数【英文标题】: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
的调用方法不是final
(T
不一定是最派生的类型)。
【参考方案1】:
一般来说,第一个选项涉及虚拟调度(即在运行时跳转到正确的函数),而第二个选项已经知道要在编译时调用的正确函数。后者通常具有较少的开销并为编译器开辟了更多优化机会,但可能存在缺点(代码大小、指令缓存等)。性能将始终取决于细节,所以如果你关心它,请介绍一下。
开箱即用的继承不能做的事情是,例如从 work_func
返回不同类型的值 - 这就是模板的亮点。
另一方面,继承(尤其是在遵循 Liskov 替换原则时)可以使接口的契约/期望更加清晰(void do_some_templated_work(T &test_class)
并没有告诉您 T
需要实现,例如 print_class_name
) .
【讨论】:
参考。性能:从一个概念更改为另一个概念相当耗时,并且需要进行大量代码更改,因此如果我不必为这两种情况分析完整代码,我会更喜欢以上是关于模板化函数或带有指向基类的指针的函数的主要内容,如果未能解决你的问题,请参考以下文章