隐式与显式接口


9

我想我了解编译时多态和运行时多态的实际限制。但是显式接口(运行时多态,即虚拟函数和指针/引用)与隐式接口(编译时多态,即模板)之间在概念上有什么区别

我的想法是,提供相同显式接口的两个对象必须是相同类型的对象(或具有共同的祖先),而提供相同隐式接口的两个对象不必是相同类型的对象,并且不包括隐式对象他们俩都提供的接口可以具有完全不同的功能。

有什么想法吗?

并且,如果两个对象提供相同的隐式接口,那么还有什么原因(除了不需要动态分配带有虚拟函数查找表的技术好处等)之外,还有其他原因导致这些对象不能从声明该接口的基础对象继承而来。使它成为显式接口?换一种说法:您能给我一个例子吗,两个提供相同隐式接口的对象(因此可以用作示例模板类的类型)不应从使该接口显式的基类继承?

一些相关的帖子:


这是使这个问题更具体的示例:

隐式接口:

class Class1
{
public:
  void interfaceFunc();
  void otherFunc1();
};

class Class2
{
public:
  void interfaceFunc();
  void otherFunc2();
};

template <typename T>
class UseClass
{
public:
  void run(T & obj)
  {
    obj.interfaceFunc();
  }
};

显式接口:

class InterfaceClass
{
public:
  virtual void interfaceFunc() = 0;
};

class Class1 : public InterfaceClass
{
public:
  virtual void interfaceFunc();
  void otherFunc1();
};

class Class2 : public InterfaceClass
{
public:
  virtual void interfaceFunc();
  void otherFunc2();
};

class UseClass
{
public:
  void run(InterfaceClass & obj)
  {
    obj.interfaceFunc();
  }
};

一个更深入的具体示例:

某些C ++问题可以通过以下任一方法解决:

  1. 模板化类,其模板类型提供隐式接口
  2. 一个非模板类,它带有提供显式接口的基类指针

不变的代码:

class CoolClass
{
public:
  virtual void doSomethingCool() = 0;
  virtual void worthless() = 0;
};

class CoolA : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that an A would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

class CoolB : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that a B would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

案例1。一个非模板类,采用带有提供显式接口的基类指针:

class CoolClassUser
{
public:  
  void useCoolClass(CoolClass * coolClass)
  { coolClass.doSomethingCool(); }
};

int main()
{
  CoolA * c1 = new CoolClass;
  CoolB * c2 = new CoolClass;

  CoolClassUser user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

情况2。一个模板化类,其模板类型提供隐式接口:

template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  CoolA * c1 = new CoolClass;
  CoolB * c2 = new CoolClass;

  CoolClassUser<CoolClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

案例3。一个模板化类,其模板类型提供隐式接口(这次,不是从派生的CoolClass

class RandomClass
{
public:
  void doSomethingCool()
  { /* Do cool stuff that a RandomClass would do */ }

  // I don't have to implement worthless()! Na na na na na!
}


template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  RandomClass * c1 = new RandomClass;
  RandomClass * c2 = new RandomClass;

  CoolClassUser<RandomClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

案例1要求被传递的对象useCoolClass()是一个孩子CoolClass(和执行worthless())。另一方面,情况2和3将采用任何具有doSomethingCool()函数的类。

如果代码的用户始终是很好的子类CoolClass,则案例1具有直觉的意义,因为CoolClassUser始终希望的实现CoolClass。但是假设此代码将成为API框架的一部分,因此我无法预测用户是否要继承CoolClass或滚动自己的具有doSomethingCool()功能的类。


也许我缺少了一些东西,但是第一段中是否已经简洁地指出了重要的区别,那就是显式接口是运行时多态,而隐式接口是编译时多态?
罗伯特·哈维

2
通过使用带指向抽象类的指针(提供显式接口)的类或函数,或使用使用提供隐式接口的对象的模板化类或函数,可以解决一些问题。两种解决方案均有效。您什么时候要使用第一个解决方案?第二?
克里斯·莫里斯

我认为,当您再打开一些概念时,大多数这些注意事项都会瓦解。例如,您在哪里适合非继承静态多态性?
哈维尔2012年

Answers:


8

您已经定义了重要的一点-一个是运行时,另一个是编译时。您需要的真正信息就是这种选择的后果。

编译时间:

  • 优点:编译时接口比运行时接口更精细。那样的话,我的意思是,您只能使用一个函数或一组函数的需求,就像调用它们一样。您不必总是做整个界面。这些需求仅是您真正需要的。
  • 优点:诸如CRTP之类的技术意味着您可以使用隐式接口对诸如运算符之类的事物进行默认实现。您永远无法通过运行时继承来做到这一点。
  • 临:隐式接口是容易编写和乘法“继承”比运行时的接口,并且不强加任何一种例如二进制限制所规限,POD类可以使用隐式接口。不需要virtual继承或具有隐式接口的其他恶作剧,这是一个很大的优势。
  • Pro:编译器可以对编译时接口进行更多优化。此外,额外的类型安全性使代码更安全。
  • 优点:不可能为运行时接口进行值输入,因为您不知道最终对象的大小或对齐方式。这意味着,任何需要/从值输入中受益的情况都将从模板中受益匪浅。
  • 缺点:模板是编译和使用的障碍,它们可以在编译器之间轻松地移植
  • 缺点:模板无法在运行时加载(显然),因此,例如,它们在表达动态数据结构方面受到限制。

运行:

  • 优点:最终类型不必在运行时确定。这意味着,如果模板可以做到的话,运行时继承可以更轻松地表达某些数据结构。您还可以跨C边界导出运行时多态类型,例如COM。
  • 优点:指定和实现运行时继承要容易得多,并且您实际上不会获得任何特定于编译器的行为。
  • 缺点:运行时继承比编译时继承慢。
  • 缺点:运行时继承会丢失类型信息。
  • 缺点:运行时继承的灵活性较差。
  • 缺点:多重继承是个bit子。

给定相对列表,如果您不需要运行时继承的特定优势,请不要使用它。与模板相比,它更慢,更不灵活且更不安全。

编辑:值得注意的是,在C ++特别是有继承使用其他比运行时多态性。例如,您可以继承typedef,或将其用于类型标记,或使用CRTP。最终,尽管这些技术(和其他技术)确实是使用实施的,但它们实际上属于“编译时” class X : public Y


关于您的第一个专业编译时间,这与我的主要问题之一有关。您是否想明确表示只想使用显式接口。就是 “我不在乎您是否拥有我需要的所有功能,如果您不继承自Z类,那么我与您无关。” 另外,使用指针/引用时,运行时继承不会丢失类型信息,对吗?
克里斯·莫里斯

@ChrisMorris:否。如果有效,那么它将起作用,这是您应该关心的。为什么要让别人在其他地方编写完全相同的代码?
jmoreno

1
@ChrisMorris:不,我不会。如果我只需要X,那么它就是封装的基本基本原理之一,我应该只要求和关心X。而且,它确实会丢失类型信息。例如,您不能通过堆栈分配此类对象。您不能使用其真实类型实例化模板。您不能在它们上调用模板化的成员函数。
DeadMG

如果您有一个使用某个类的类Q,那该怎么办?Q带有一个模板参数,因此任何提供隐式接口的类都可以使用,或者我们认为如此。事实证明,类Q还期望其内部类(称为H)使用Q的接口。例如,当H对象被破坏时,它应该调用Q的某个函数。不能在隐式接口中指定。因此,模板失败。更明确地说,一组紧密耦合的类彼此之间不仅需要隐式接口,而且似乎禁止使用模板。
克里斯·莫里斯

Con编译时:难以调试,需要将定义放入标头中
JFFIGK
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.