隐式与显式接口
implicit vs explicit interfaces
在下面的例子中,使用隐式接口(案例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()函数的类。
如果代码的用户总是很好地将CoolClass进行子类化,那么情况1就有直观的意义,因为酷类用户总是期望实现酷类。但假设此代码将是API框架的一部分,因此我无法预测用户是否希望将CoolClass子类化或滚动具有doSomethingCool()
一些相关帖子:
https://stackoverflow.com/a/7264550/635125
https://stackoverflow.com/a/7264689/635125
https://stackoverflow.com/a/8009872/635125
我想到的一些考虑因素,说明您为什么更喜欢案例1:
- 如果
CoolClass
不是纯接口,即实现的一部分也被继承(尽管您也可以为案例2/3提供它,例如以基类的形式) - 如果有理由将
CoolClassUser
实现为二进制而不是标头(这不仅是保护,还可能是代码大小、资源控制、集中错误处理等) - 如果你想存储指针并在以后使用它,那么情况1似乎也更好:(a)将它们都保存在同一个容器中更容易,(b)你也需要存储实际的数据类型,对于情况2/3,想到的解决方案是在模板包装器的帮助下将其转换为"显式"接口(即情况1)
案例2/3可能更可取的原因:
- 如果您稍后决定
worthless()
现在有价值,并开始使用它,那么在案例2中,对于未实现它的类,您将得到编译时错误。在案例1中,没有什么会提醒您真正实现这些函数,除非您运气不好,可能会出现运行时错误 - 案例2/3可能具有稍好的性能,尽管代价是较大的代码大小
在某些情况下,这可能纯粹是个人偏好的问题,无论是你的还是你的用户。
请记住,在情况#2和#3中,您依赖于模板参数,这意味着编码器在调用时必须使用正确的类型正确地实例化模板参数。根据函数的使用方式,这可能会产生一些问题,您希望为用户创建一个抽象接口,而不必担心传递的对象类型。。。即指向使用多态性将对象从一个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*
而设计的函数实例化模板,这只会为用户键入更多的样板代码。
- C++核心准则 C35 对于接口类"A base class destructor should be either public and virtual, or protected and nonv
- Visual C++GC接口如何启用它以及要包含哪个库
- Windows.h与GLFW.h的接口
- 当字段可以为null时,如何使用C++接口在Avro中写入数据
- 提供与TMP和SFINAE的通用接口
- 为重写std::exception的库生成swig接口时出错
- 内联如何影响模块接口中的成员函数
- COM 接口 c# 封送数组数组
- 如何在 SCIP C++ 接口中获取 MILP 约束矩阵中的系数值
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 如何绑定 C++ gRPC 客户端的网络接口
- 模板化接口 - 创建一个泛型模板类以返回任何容器
- 如何从实现接口的模板化类实例访问结构
- 带有进度表的 curl 多接口程序
- 设计帮助 - 为不同类型的消息处理通用接口的设计模式
- 我可以在具有一个标头和一个接口的 cpp 文件中有多个嵌入吗?
- 类接口,可以创建N个方法
- 类具有相同的接口,但参数的类型不同
- 如何与 Cheerp/js 中的 extern 变量接口?
- 如何使用现代 CMake 安装捆绑的接口依赖项?