如何在 C++ 中模拟接口?
由于 C++ 缺乏 Java 和 C# 的接口功能,那么在 C++ 类中模拟接口的首选方法是什么? 我的猜测是抽象类的多重继承。 对内存开销/性能有何影响? 此类模拟接口是否有任何命名约定,例如 SerializedInterface
?
Since C++ lacks the interface
feature of Java and C#, what is the preferred way to simulate interfaces in C++ classes? My guess would be multiple inheritance of abstract classes.
What are the implications in terms of memory overhead/performance?
Are there any naming conventions for such simulated interfaces, such as SerializableInterface
?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
由于与 C# 和 Java 不同,C++ 具有多重继承,因此您可以创建一系列抽象类。
至于约定,就看你自己了; 不过,我喜欢在类名前加上 I。
与 C# 和 Java 相比,性能无需担心。 基本上,您将只需要为您的函数或 vtable 提供一个查找表的开销,就像任何带有虚拟方法的继承所给出的那样。
Since C++ has multiple inheritance unlike C# and Java, yes you can make a series of abstract classes.
As for convention, it is up to you; however, I like to precede the class names with an I.
The performance is nothing to worry about in terms of comparison between C# and Java. Basically you will just have the overhead of having a lookup table for your functions or a vtable just like any sort of inheritance with virtual methods would have given.
实际上没有必要“模拟”任何东西,因为 C++ 并没有缺少 Java 可以用接口做的任何事情。
从 C++ 指针的角度来看,Java 在
接口
和类
之间进行了“人为”区分。接口
只是一个类
,其所有方法都是抽象的,并且不能包含任何数据成员。Java 做出此限制是因为它不允许不受约束的多重继承,但它确实允许
类
实现
多个接口。在 C++ 中,
类
是一个类
,接口
是一个类
。extends
是通过公共继承实现的,implements
也是通过公共继承实现的。从多个非接口类继承可能会导致额外的复杂性,但在某些情况下可能很有用。 如果您限制自己只能从最多一个非接口类和任意数量的完全抽象类继承类,那么您将不会遇到 Java 中遇到的任何其他困难(其他 C++/Java 差异除外)。
就内存和开销成本而言,如果您要重新创建 Java 样式的类层次结构,那么无论如何您可能已经为类支付了虚拟函数成本。 鉴于您无论如何都使用不同的运行时环境,就不同继承模型的成本而言,两者之间的开销不会有任何根本差异。
There's really no need to 'simulate' anything as it is not that C++ is missing anything that Java can do with interfaces.
From a C++ pointer of view, Java makes an "artificial" disctinction between an
interface
and aclass
. Aninterface
is just aclass
all of whose methods are abstract and which cannot contain any data members.Java makes this restriction as it does not allow unconstrained multiple inheritance, but it does allow a
class
toimplement
multiple interfaces.In C++, a
class
is aclass
and aninterface
is aclass
.extends
is achieved by public inheritance andimplements
is also achieved by public inheritance.Inheriting from multiple non-interface classes can result in extra complications but can be useful in some situations. If you restrict yourself to only inheriting classes from at most one non-interface class and any number of completely abstract classes then you aren't going to encounter any other difficulties than you would have in Java (other C++ / Java differences excepted).
In terms of memory and overhead costs, if you are re-creating a Java style class hierarchy then you have probably already paid the virtual function cost on your classes in any case. Given that you are using different runtime environments anyway, there's not going to be any fundamental difference in overhead between the two in terms of cost of the different inheritance models.
“对内存开销/性能有什么影响?”
尽管标准在性能方面没有提供太多保证,但通常除了使用虚拟调用之外,没有任何其他方法。
在内存开销方面,“空基类”优化显式允许编译器布局结构,以便添加没有数据成员的基类不会增加对象的大小。 我认为您不太可能需要处理不执行此操作的编译器,但我可能是错的。
与没有虚拟成员函数的情况相比,向类添加第一个虚拟成员函数通常会增加对象的指针大小。 添加更多虚拟成员函数不会产生额外的差异。 添加虚拟基类可能会产生进一步的影响,但对于您所讨论的内容,您不需要这样做。
添加具有虚拟成员函数的多个基类可能意味着实际上您只能获得一次空基类优化,因为在典型的实现中,该对象将需要多个 vtable 指针。 因此,如果每个类需要多个接口,则可能会增加对象的大小。
在性能方面,虚拟函数调用比非虚拟函数调用有一点点额外的开销,更重要的是,您可以假设它通常(总是?)不会被内联。 添加空基类通常不会向构造或析构添加任何代码,因为空基类构造函数和析构函数可以内联到派生类构造函数/析构函数代码中。
如果您想要显式接口,但不需要动态多态性,可以使用一些技巧来避免虚拟函数。 但是,如果您尝试模拟 Java,那么我认为情况并非如此。
示例代码:
输出(32 位平台上的 GCC):
"What are the implications in terms of memory overhead/performance?"
Usually none except those of using virtual calls at all, although nothing much is guaranteed by the standard in terms of performance.
On memory overhead, the "empty base class" optimization explicitly permits the compiler to layout structures such that adding a base class which has no data members does not increase the size of your objects. I think you're unlikely to have to deal with a compiler which does not do this, but I could be wrong.
Adding the first virtual member function to a class usually increases objects by the size of a pointer, compared with if they had no virtual member functions. Adding further virtual member functions makes no additional difference. Adding virtual base classes might make a further difference, but you don't need that for what you're talking about.
Adding multiple base classes with virtual member functions probably means that in effect you only get the empty base class optimisation once, because in a typical implementation the object will need multiple vtable pointers. So if you need multiple interfaces on each class, you may be adding to the size of the objects.
On performance, a virtual function call has a tiny bit more overhead than a non-virtual function call, and more importantly you can assume that it generally (always?) won't be inlined. Adding an empty base class doesn't usually add any code to construction or destruction, because the empty base constructor and destructor can be inlined into the derived class constructor/destructor code.
There are tricks you can use to avoid virtual functions if you want explicit interfaces, but you don't need dynamic polymorphism. However, if you're trying to emulate Java then I assume that's not the case.
Example code:
Output (GCC on a 32bit platform):
C++ 中的接口是仅具有纯虚函数的类。 例如:
这不是一个模拟接口,它是一个类似于Java中的接口,但没有缺点。
例如,您可以添加方法和成员,而不会产生负面后果:
对于命名约定... C++ 语言中没有定义真正的命名约定。 因此,请选择适合您环境的一个。
开销是 1 个静态表,并且在尚未具有虚函数的派生类中,有一个指向静态表的指针。
Interfaces in C++ are classes which have only pure virtual functions. E.g. :
This is not a simulated interface, it is an interface like the ones in Java, but does not carry the drawbacks.
E.g. you can add methods and members without negative consequences :
To the naming conventions ... there are no real naming conventions defined in the C++ language. So choose the one in your environment.
The overhead is 1 static table and in derived classes which did not yet have virtual functions, a pointer to the static table.
在 C++ 中,我们可以比 Java 和 Java 的简单无行为接口走得更远。 公司
我们可以使用 NVI 模式添加显式契约(如Design by Contract)。
In C++ we can go further than the plain behaviour-less interfaces of Java & co.
We can add explicit contracts (as in Design by Contract) with the NVI pattern.
通过记录模板类型参数的要求,C++ 中的接口也可以静态出现。
模板模式匹配语法,因此您不必预先指定特定类型实现特定接口,只要它具有正确的成员即可。 这与 Java 的
或 C# 的
where T : IInterface
样式约束,要求替换类型了解 (I
)Interface
。一个很好的例子是 Iterator 系列,它们是通过指针等实现的。
Interfaces in C++ can also occur statically, by documenting the requirements on template type parameters.
Templates pattern match syntax, so you don't have to specify up front that a particular type implements a particular interface, so long as it has the right members. This is in contrast to Java's
<? extends Interface>
or C#'swhere T : IInterface
style constraints, which require the substituted type to know about (I
)Interface
.A great example of this is the Iterator family, which are implemented by, among other things, pointers.
如果不使用虚拟继承,那么开销应该不会比具有至少一个虚拟函数的常规继承差。 继承的每个抽象类都会添加一个指向每个对象的指针。
但是,如果您执行空基类优化之类的操作,则可以将其最小化:
C 的大小将是两个单词。
If you don't use virtual inheritance, the overhead should be no worse than regular inheritance with at least one virtual function. Each abstract class inheritted from will add a pointer to each object.
However, if you do something like the Empty Base Class Optimization, you can minimize that:
The size of C will be two words.
顺便说一句,MSVC 2008 有 __interface 关键字。
此功能是 Microsoft 特有的。 注意:__interface 没有虚拟析构函数如果通过接口指针删除对象,则需要。
By the way MSVC 2008 has __interface keyword.
This feature is Microsoft Specific. Caution: __interface has no virtual destructor that is required if you delete objects by its interface pointers.
没有好的方法可以按照您要求的方式实现接口。 诸如完全抽象 ISerialized 基类之类的方法的问题在于 C++ 实现多重继承的方式。 请考虑以下情况:
显然,函数 toSerial() 的目的是序列化 Subclass 的所有成员,包括它从 Base 类继承的成员。 问题是没有从 ISerialized 到 Base 的路径。 如果执行以下命令,您可以以图形方式看到这一点:
第一次调用输出的值与第二次调用输出的值不同。 即使在这两种情况下都会传递对 s 的引用,编译器也会调整传递的地址以匹配正确的基类类型。
There is no good way to implement an interface the way you're asking. The problem with an approach such as as completely abstract ISerializable base class lies in the way that C++ implements multiple inheritance. Consider the following:
Clearly the intent is for the function toSerial() to serialize all of the members of Subclass including those that it inherits from Base class. The problem is that there is no path from ISerializable to Base. You can see this graphically if you execute the following:
The value output by the first call is not the same as the value output by the second call. Even though a reference to s is passed in both cases, the compiler adjusts the address passed to match the proper base class type.