因为 C++ 缺乏 Java 和 C# 的 interface
特性,那么在 C++ 类中模拟接口的首选方式是什么?我的猜测是使用抽象类的多重继承。
这种模拟接口的方式对内存开销/性能会有什么影响吗?
是否有任何命名约定用于此类模拟接口,例如 SerializableInterface
?
因为 C++ 缺乏 Java 和 C# 的 interface
特性,那么在 C++ 类中模拟接口的首选方式是什么?我的猜测是使用抽象类的多重继承。
这种模拟接口的方式对内存开销/性能会有什么影响吗?
是否有任何命名约定用于此类模拟接口,例如 SerializableInterface
?
因为C++支持多重继承,而C#和Java不支持,所以你可以创建一系列的抽象类。
至于命名规范,由你决定;然而,我喜欢在类名前加上一个I。
class IStringNotifier
{
public:
virtual void sendMessage(std::string &strMessage) = 0;
virtual ~IStringNotifier() { }
};
就性能而言,C#和Java之间的比较并无需担心。基本上,你只需要为函数建立一个查找表或虚函数表(vtable),就像任何具有虚方法的继承一样,这样做会带来一些开销。
没有必要“模拟”任何内容,因为C++没有缺少Java可以使用接口实现的功能。
从C++的角度来看,Java在interface
和class
之间做了一个“人为”的区分。 interface
只是一个所有方法都是抽象的class
,而且不能包含任何数据成员。
Java之所以会有这样的限制是因为它不允许无限制的多重继承,但它确实允许一个class
实现多个接口。
在C++中,class
就是class
,而interface
也是class
。通过公共继承实现extends
,通过公共继承实现implements
。
从多个非接口类进行继承可能会导致额外的复杂性,但在某些情况下可能会很有用。如果你限制自己最多只继承一个非接口类和任意数量的完全抽象类,则不会遇到除Java之外的其他困难(当然还有其他C++/Java的不同之处)。
在内存和开销方面,如果您正在重新创建Java风格的类层次结构,则您可能已经在类上支付了虚函数的成本。考虑到您正在使用不同的运行时环境,因此在不同继承模型的开销方面并没有根本性的差异。
#include <iostream>
// A is an interface
struct A {
virtual ~A() {};
virtual int a(int) = 0;
};
// B is an interface
struct B {
virtual ~B() {};
virtual int b(int) = 0;
};
// C has no interfaces, but does have a virtual member function
struct C {
~C() {}
int c;
virtual int getc(int) { return c; }
};
// D has one interface
struct D : public A {
~D() {}
int d;
int a(int) { return d; }
};
// E has two interfaces
struct E : public A, public B{
~E() {}
int e;
int a(int) { return e; }
int b(int) { return e; }
};
int main() {
E e; D d; C c;
std::cout << "A : " << sizeof(A) << "\n";
std::cout << "B : " << sizeof(B) << "\n";
std::cout << "C : " << sizeof(C) << "\n";
std::cout << "D : " << sizeof(D) << "\n";
std::cout << "E : " << sizeof(E) << "\n";
}
输出结果(32位平台上的GCC):
A : 4
B : 4
C : 8
D : 8
E : 12
C++中的接口是只包含纯虚函数的类。例如:
class ISerializable
{
public:
virtual ~ISerializable() = 0;
virtual void serialize( stream& target ) = 0;
};
这不是一个模拟接口,它是一种类似于Java中的接口,但没有缺点。
例如,您可以添加方法和成员而不会产生负面影响:
class ISerializable
{
public:
virtual ~ISerializable() = 0;
virtual void serialize( stream& target ) = 0;
protected:
void serialize_atomic( int i, stream& t );
bool serialized;
};
在C++语言中,没有真正定义命名约定。因此,请选择适合您环境的命名约定。
开销为1个静态表和在尚未具有虚函数的派生类中,一个指向静态表的指针。
在C++中,我们可以比Java等语言的纯粹无行为接口更进一步。我们可以使用NVI模式添加显式契约(如“按合同设计”)。
struct Contract1 : noncopyable
{
virtual ~Contract1();
Res f(Param p) {
assert(f_precondition(p) && "C1::f precondition failed");
const Res r = do_f(p);
assert(f_postcondition(p,r) && "C1::f postcondition failed");
return r;
}
private:
virtual Res do_f(Param p) = 0;
};
struct Concrete : virtual Contract1, virtual Contract2
{
...
};
<? extends Interface>
或 C# 的 where T : IInterface
约束风格形成对比,后者要求替换类型知道(I
)Interface
。A Visual C++ interface can be defined as follows:
- Can inherit from zero or more base
interfaces.
- Cannot inherit from a base class.
- Can only contain public, pure virtual
methods.
- Cannot contain constructors,
destructors, or operators.
- Cannot contain static methods.
- Cannot contain data members;
properties are allowed.
此功能仅适用于Microsoft。注意:如果您通过其接口指针删除对象,则__interface没有必需的虚析构函数。
如果您不使用虚拟继承,那么开销应该不会比至少有一个虚函数的常规继承更糟糕。每个抽象类继承都会向每个对象添加一个指针。
但是,如果您像空基类优化一样做一些事情,就可以将其最小化:
struct A { void func1() = 0; };
struct B: A { void func2() = 0; };
struct C: B { int i; };
C 的大小将为两个字。
按照您的要求,没有好的方法来实现接口。采用完全抽象的ISerializable基类的方法存在问题,这是由于C++实现多重继承的方式所导致的。请考虑以下情况:
class Base
{
};
class ISerializable
{
public:
virtual string toSerial() = 0;
virtual void fromSerial(const string& s) = 0;
};
class Subclass : public Base, public ISerializable
{
};
void someFunc(fstream& out, const ISerializable& o)
{
out << o.toSerial();
}
显然,函数toSerial()的意图是序列化Subclass的所有成员,包括从Base类继承的成员。问题是ISerializable到Base之间没有路径。如果您执行以下操作,则可以在图形上看到这一点:
void fn(Base& b)
{
cout << (void*)&b << endl;
}
void fn(ISerializable& i)
{
cout << (void*)&i << endl;
}
void someFunc(Subclass& s)
{
fn(s);
fn(s);
}