【问题标题】:implicit vs explicit interfaces [closed]隐式与显式接口
【发布时间】:2012-02-10 18:37:33
【问题描述】:

在以下示例中,使用隐式接口(案例 2 和 3;模板)与使用显式接口(案例 1;指向抽象类的指针)的优缺点是什么?

不变的代码:

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()
{
  CoolClass * c1 = new CoolA;
  CoolClass * c2 = new CoolB;

  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()
{
  CoolClass * c1 = new CoolA;
  CoolClass * c2 = new CoolB;

  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() 函数的 any 类。

如果代码的用户总是可以很好地继承 CoolClass,那么案例 1 就很直观了,因为 CoolClassUser 总是期待 CoolClass 的实现。但假设这段代码将成为 API 框架的一部分,所以我无法预测用户是否想要继承 CoolClass 或推出他们自己的具有 doSomethingCool() 函数的类。

一些相关的帖子:

https://stackoverflow.com/a/7264550/635125

https://stackoverflow.com/a/7264689/635125

https://stackoverflow.com/a/8009872/635125

【问题讨论】:

  • 您的案例 1 和案例 2 无法编译。指针初始化走错路了。

标签: c++ templates interface


【解决方案1】:

我想到了为什么您更喜欢案例 1 的一些考虑:

  • 如果CoolClass 不是纯接口,即部分实现也被继承(尽管您也可以为案例 2/3 提供它,例如以基类的形式);
  • 如果有理由将 CoolClassUser 实现为二进制而不是标头(这不仅是保护,还可能是代码大小、资源控制、集中错误处理等);
  • 如果您想存储指针并在以后使用它,那么案例 1 似乎也更好:(a) 将它们全部保存在同一个容器中更容易,并且 (b) 您需要将实际数据类型存储为好吧,对于案例 2/3,想到的解决方案是借助模板包装器将其转换为“显式”接口(即案例 1)。

案例 2/3 可能更可取的原因:

  • 如果您后来决定 worthless() 现在值得并开始使用它,在案例 2 中,您将收到未实现的类的编译时错误。在案例 1 中,没有什么会提醒您真正实现这些功能,除非您(不)走运可能会出现运行时错误。
  • Case2/3 的性能可能稍好一些,但代价是代码量更大。

在某些情况下,这可能纯粹是个人喜好问题,无论是您的还是您的用户。

【讨论】:

    【解决方案2】:

    请记住,在第 2 和第 3 种情况下,您依赖于模板参数,这意味着调用时的编码器必须使用正确的类型正确实例化模板参数。根据函数的使用方式,这可能会产生一些问题,您希望为用户创建一个抽象接口,而他们不必担心传递的对象的类型......即“句柄”或一些other 指向派生对象的指针,该派生对象使用多态性将对象从一个 API 函数传递到另一个。例如:

    class abstract_base_class;
    
    abtract_base_class* get_handle();
    void do_something_with_handle(abstract_base_class* handle);
    void do_something_else_with_handle(abstract_base_class* handle);
    //... more API functions
    

    现在,您的 API 框架可以将一个对象传回给您的代码的用户,他们不需要知道该对象是什么......他们只需要知道它描述了某种类型的接口,您当然可以在某个地方的标题中公开公开。但是他们不必知道您传回给他们的对象的“内脏”。您可以给它们一个指向您控制其实现的派生类型的指针。您只需为 API 中最通用的函数类型提供模板。否则,必须为仅设计用于获取 abstract_base_class* 的函数实例化模板只会为用户键入更多样板代码。

    【讨论】:

      猜你喜欢
      • 2010-10-21
      • 2010-10-10
      • 1970-01-01
      • 2011-09-02
      • 2019-04-03
      • 2010-09-07
      • 1970-01-01
      • 2011-06-15
      相关资源
      最近更新 更多