C++接口与模板

Mir*_*pas 28 c++ design-patterns

我有两个针对同一问题的解决方案 - 从一个"控制器"到使用过的对象进行某种回调,我不知道该选择什么.

解决方案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;
};
Run Code Online (Sandbox Code Playgroud)

解决方案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;
};
Run Code Online (Sandbox Code Playgroud)

这只是一个简单的示例来说明我的问题.在现实世界中,接口将具有多个功能,并且一个类可以(并且将!)实现多个接口.

代码不会被用作外部项目的库,我不必隐藏模板实现 - 我这样说是因为如果我需要隐藏"控制器"实现,第一种情况会更好.

您能否告诉我每个案例的优点/缺点以及哪些更好用?

Dav*_*eas 49

在我看来,性能应该被忽略(不是真的,但微观优化应该),直到你有理由这样做.如果没有一些硬性要求(这是一个占用大部分CPU的紧凑循环,接口成员函数的实际实现非常小......)如果不是不可能注意到差异,那将是非常困难的.

所以我会专注于更高的设计水平.所有类型的UseA共享基础是否有意义?他们真的相关吗?这些类型之间是否存在明确关系?然后OO方法可能会起作用.他们不相关吗?也就是说,他们是否有共同的特征,但没有直接关系,你可以建模?去模板方法.

模板的主要优点是您可以使用不符合特定和完全继承层次结构的类型.例如,您可以将任何内容存储在可复制构造的向量中(在C++ 11中可移动构造),但是a int和a Car在任何方面都不是真正相关的.这样,您可以减少与您的类型一起使用的不同类型之间的耦合UseA.

模板的一个缺点是每个模板实例化都是与同一基本模板生成的其余模板实例无关的不同类型.这意味着你不能存储UseA<A>UseA<B>相同的容器内,将有代码膨胀(UseA<int>::fooUseA<double>::foo两者都在二进制生成),更长的编译时间(即使不考虑额外的功能使用两个翻译单元UseA<int>::foo都将产生同样的功能,链接器将不得不丢弃其中一个).

关于其他答案声称的表现,他们在某种程度上是正确的,但大多数人都错过了重点.选择模板而非动态调度的主要优点不是动态调度的额外开销,而是编译器可以内联小函数(如果函数定义本身可见).

如果函数没有内联,除非函数只执行很少的循环,否则函数的总成本将超过动态调度的额外成本(即调用中的额外间接以及this指针在案例中的可能偏移量)多个/虚拟继承).如果函数执行某些实际工作,和/或它们无法内联,则它们将具有相同的性能.

即使在少数情况下,一种方法与另一种方法的性能差异可以衡量(假设这些函数只需要两个周期,并且调度因此每个函数的成本加倍),如果这个代码是80%的一部分,那么占用不到20%的cpu时间的代码,并说这段特殊的代码占用了cpu的1%(如果考虑到性能明显,函数本身必须采用的前提,这是一个巨大的数量)一个或两个周期!)然后你谈论1小时程序运行30秒.再次检查前提,在2GHz cpu上,1%的时间意味着该功能必须每秒调用超过1000万次.

所有这些都是挥手,并且它与其他答案一样落在相反的方向上(即有一些不精确的情况可能会使它看起来好像差异小于实际值,但现实更接近这个比它更接近一般的答案动态调度会使你的代码变慢.

  • 这应该是最好的答案.我想两次投票. (2认同)

Ste*_*eve 19

各有利弊.从C++编程语言:

  1. 当运行时效率非常高时,首选模板而不是派生类.
  2. 如果在不重新编译的情况下添加新变体,则首选模板上的派生类非常重要.
  3. 当不能定义公共基础时,首选模板而不是派生类.
  4. 当具有兼容性约束的内置类型和结构很重要时,首选模板而不是派生类.

但是,模板有其缺点

  1. 只要模板强制公开头文件中的整个代码,使用OO接口的代码就可以隐藏在.cpp/.CC文件中;
  2. 模板会导致代码膨胀;
  3. 每当模板参数的要求是隐含的并且只存在于开发人员的头脑中时,OO接口是显式的;
  4. 大量使用模板会损害编译速度.

使用哪个取决于您的情况,取决于您的偏好.模板化代码可能会产生一些钝的编译错误,导致诸如STL错误解密之类的工具.希望概念很快就会实施.


Rei*_*ica 18

模板案例的性能稍好一些,因为不涉及虚拟调用.如果非常频繁地使用回调,请使用模板解决方案.请注意,"非常频繁"并不会真正开始,直到涉及到每秒数千,甚至可能更晚.

另一方面,模板必须位于头文件中,这意味着对它的每次更改都会强制重新编译调用它的所有站点,这与接口场景不同,在接口场景中,实现可能位于.cpp中并且是唯一需要的文件重新编译.

  • +1有关重新编译成本的好处. (3认同)
  • 您还可以提到每个实例化虚拟表的几个字节的开销(取决于编译器实现) (2认同)