问题描述
对于相同的问题,我有2个解决方案-从一个控制器到使用的对象进行某种回调,而我不知道该选择什么。
I have 2 solutions for the same problem - to make some kind of callbacks from one "controller" to the used object and I don't know what to chose.
解决方案1:使用接口
struct AInterface
{
virtual void f() = 0;
};
struct A : public AInterface
{
void f(){std::cout<<"A::f()"<<std::endl;}
};
struct UseAInterface
{
UseAInterface(AInterface* a) : _a(a){}
void f(){_a->f();}
AInterface* _a;
};
解决方案2:使用模板
struct A
{
void f(){std::cout<<"A::f()"<<std::endl;}
};
template<class T>
struct UseA
{
UseA(T* a) : _a(a){}
void f(){_a->f();}
T* _a;
};
这只是一个简单的示例来说明我的问题。在现实世界中,该接口将具有多个功能,并且一个类可以(并且将!)实现多个接口。
This is just a simple sample to illustrate my problem. In real world the interface will have several functions and one class may(and will!) implement multiple interfaces.
该代码不会用作外部项目的库,并且我不必隐藏模板实现-之所以这样说是因为,如果我需要隐藏控制器实现,则第一种情况会更好。
The code will not be used as a library for external projects and I don't have to hide the template implementation - I say this because first case will be better if I need to hide "controller" implementation.
可以告诉我吗每种情况的优点/缺点以及使用哪种更好的方法?
Can you please tell me the advantages/disadvantages for each case and what is better to use?
推荐答案
在我看来,效果应该忽略(不是真的,但是应该进行微优化),直到您有理由这样做为止。没有一些硬性要求(这是占用大量CPU的紧密循环,接口成员函数的实际实现非常小...),即使不是不可能,也很难注意到差异。
In my opinion performance should be ignored (not really, but micro optimizations should) until you have a reason for that. Without some hard requirements (this is in a tight loop that takes most of the CPU, the actual implementations of the interface member functions is very small...) it would be very hard if not impossible to notice the difference.
所以我将重点放在更高的设计水平上。 UseA
中使用的所有类型都具有相同的基础是否有意义?他们真的有关系吗?类型之间是否存在明确的 is-a 关系?然后,OO方法可能会起作用。他们无关吗?也就是说,它们是否具有某些特征,但是没有可以建模的直接 is-a 关系?选择模板方法。
So I would focus on a higher design level. Does it make sense that all types used in UseA
share a common base? Are they really related? Is there a clear is-a relationship between the types? Then the OO approach might work. Are they unrelated? That is, do they share some traits but there is no direct is-a relationship that you can model? Go for the template approach.
模板的主要优点是您可以使用不符合特定且确切的继承层次结构的类型。例如,您可以将任何内容存储在可复制构造的向量中(在C ++ 11中是可移动构造的),但可以存储 int
和汽车
并没有任何关系。这样,您可以减少与 UseA
类型一起使用的不同类型之间的耦合。
The main advantage of the template is that you can use types that don't conform to a particular and exact inheritance hierarchy. For example, you can store anything in a vector that is copy-constructible (move-constructible in C++11), but an int
and a Car
are not really related in any ways. This way, you reduce the coupling between the different types used with your UseA
type.
其中一个缺点template是每个模板实例化都是不同的类型,与从同一基本模板生成的其余模板实例化无关。这意味着您不能将 UseA< A>
和 UseA< B>
存储在同一容器中, em> code-bloat ( UseA< int> :: foo
和 UseA< double> :: foo
两者都是在二进制文件中生成的),编译时间更长(即使不考虑额外的功能,使用 UseA< int> :: foo
的两个翻译单元也会生成相同的
One of the disadvantages of templates is that each template instantiation is a different type that is unrelated to the rest of the template instantiations generated out of the same base template. This means that you cannot store UseA<A>
and UseA<B>
inside the same container, there will be code-bloat (UseA<int>::foo
and UseA<double>::foo
both are generated in the binary), longer compile times (even without considering the extra functions, two translation units that use UseA<int>::foo
will both generate the same function, and the linker will have to discard one of them).
关于其他答案所声称的性能,它们在某种程度上是正确的,但大多数都忽略了要点。选择模板而不是动态调度的主要优点不是动态调度的额外开销,而是编译器可以内联小函数的事实(如果函数定义本身可见)。
Regarding the performance that other answers claim, they are somehow right, but most miss the important points. The main advantage of choosing templates over dynamic dispatch is not the extra overhead of the dynamic dispatch, but the fact that small functions can be inlined by the compiler (if the function definition itself is visible).
如果未内联函数,除非函数只需执行很少的周期,否则函数的总体成本将超过动态调度的额外成本(即额外的间接调用,以及在多重/虚拟继承的情况下 this
指针的可能偏移量)。如果函数执行某些实际工作,并且/或者无法内联函数,则它们将具有相同的性能。
If the functions are not inlined, unless the function takes just very few cycles to execute, the overall cost of the function will trump the extra cost of dynamic dispatch (i.e. the extra indirection in the call and the possible offset of the this
pointer in the case of multiple/virtual inheritance). If the functions do some actual work, and/or they cannot be inlined they will have the same performance.
即使在少数情况下,一种方法的性能差异如果此代码是80%的代码中少于20%的cpu时间的一部分,那么从另一个代码中得出的代码是可以测量的(例如,这些函数仅需两个周期,并且分派因此使每个函数的成本增加了一倍),并说这段特定的代码占用了1%的cpu(如果您考虑到要使性能引人注目的功能本身必须仅花费一个或两个周期,这是一个巨大的数目),那么您正在谈论30秒1小时的程序运行时间。再次检查前提,在2GHz cpu上,1%的时间意味着该函数必须每秒被调用1000万次以上。
Even in the few cases where the difference in performance of one approach from the other could be measurable (say that the functions only take two cycles, and that dispatch thus doubles the cost of each function) if this code is part of the 80% of the code that takes less than 20% of the cpu time, and say that this particular piece of code takes 1% of the cpu (which is a huge amount if you consider the premise that for performance to be noticeable the function itself must take just one or two cycles!) then you are talking about 30 seconds out of 1 hour program run. Checking the premise again, on a 2GHz cpu, 1% of the time means that the function would have to be called over 10 million times per second.
以上所有是挥舞着的手,它与其他答案在相反的方向上落下(即有些不确定性可能使差异似乎小于其实际值,但实际情况比一般答案更接近于此) 动态调度会使您的代码变慢。
All of the above is hand waving, and it is falling on the opposite direction as the other answers (i.e. there are some imprecisions could make it seem as if the difference is smaller than it really is, but reality is closer to this than it is to the general answer dynamic dispatch will make your code slower.
这篇关于C ++接口与模板的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!