Answers:
为了扩展bradtgmurray的答案,您可能需要通过添加虚拟析构函数来对接口的纯虚拟方法列表进行例外处理。这使您可以将指针所有权传递给另一方,而无需暴露具体的派生类。析构函数不需要执行任何操作,因为接口没有任何具体成员。将函数定义为虚拟函数和内联函数似乎很矛盾,但是请相信我,事实并非如此。
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Parent
{
public:
virtual ~Parent();
};
class Child : public Parent, public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};
您不必包括虚拟析构函数的主体-事实证明,某些编译器在优化空析构函数时遇到了麻烦,最好使用默认值。
=0
使用主体定义纯虚拟()析构函数。这样做的好处是,从理论上讲,编译器可以看到vtable现在没有有效的成员,并将其完全丢弃。对于带有主体的虚拟析构函数,可以在构造过程中(例如,通过this
指针)(当所构造的对象仍为Parent
类型时)调用(虚拟)该析构函数,因此编译器必须提供有效的vtable。因此,如果this
在构造过程中不显式调用虚拟析构函数:),可以节省代码大小。
override
关键字以允许编译时参数和返回值类型检查。例如,在宣告Childvirtual void OverrideMe() override;
使用纯虚方法创建类。通过创建另一个覆盖这些虚拟方法的类来使用该接口。
纯虚拟方法是定义为虚拟并分配给0的类方法。
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Child : public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};
override
在C ++ 11中使用
在C#/ Java中除了抽象基类之外,还具有特殊的接口类型类别的全部原因是,因为C#/ Java不支持多重继承。
C ++支持多重继承,因此不需要特殊类型。没有非抽象(纯虚拟)方法的抽象基类在功能上等效于C#/ Java接口。
Thread
实例。多重继承可能是不良的设计,也可能是不良的组合。这全视情况而定。
C ++中本身没有“接口”的概念。AFAIK,首先在Java中引入接口来解决缺少多重继承的问题。事实证明,此概念非常有用,并且可以通过使用抽象基类在C ++中实现相同的效果。
抽象基类是其中至少一个成员函数(在Java语言中为方法)是使用以下语法声明的纯虚函数的类:
class A
{
virtual void foo() = 0;
};
抽象的基类不能被实例化,即您不能声明类A的对象。您只能从A派生类,但是任何不提供实现的派生类foo()
也将是抽象的。为了避免变得抽象,派生类必须为其继承的所有纯虚函数提供实现。
请注意,抽象基类可以不仅仅是接口,因为它可以包含非纯虚拟的数据成员和成员函数。等效的接口将是抽象基类,而没有任何只有纯虚函数的数据。
而且,正如Mark Ransom所指出的,就这一点而言,抽象基类应该像任何基类一样提供虚拟析构函数。
据我测试,添加虚拟析构函数非常重要。我正在使用用创建new
并销毁的对象delete
。
如果没有在接口中添加虚拟析构函数,则不会调用继承类的析构函数。
class IBase {
public:
virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
virtual void Describe() = 0; // pure virtual method
};
class Tester : public IBase {
public:
Tester(std::string name);
virtual ~Tester();
virtual void Describe();
private:
std::string privatename;
};
Tester::Tester(std::string name) {
std::cout << "Tester constructor" << std::endl;
this->privatename = name;
}
Tester::~Tester() {
std::cout << "Tester destructor" << std::endl;
}
void Tester::Describe() {
std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}
void descriptor(IBase * obj) {
obj->Describe();
}
int main(int argc, char** argv) {
std::cout << std::endl << "Tester Testing..." << std::endl;
Tester * obj1 = new Tester("Declared with Tester");
descriptor(obj1);
delete obj1;
std::cout << std::endl << "IBase Testing..." << std::endl;
IBase * obj2 = new Tester("Declared with IBase");
descriptor(obj2);
delete obj2;
// this is a bad usage of the object since it is created with "new" but there are no "delete"
std::cout << std::endl << "Tester not defined..." << std::endl;
descriptor(new Tester("Not defined"));
return 0;
}
如果在不带的情况下运行前面的代码virtual ~IBase() {};
,您将看到析构函数Tester::~Tester()
永远不会被调用。
我的答案与其他答案基本相同,但我认为还有两件事要做:
在界面中声明一个虚拟析构函数,或制作一个受保护的非虚拟析构函数,以避免在有人尝试删除类型为的对象时发生未定义的行为IDemo
。
使用虚拟继承可以避免多重继承带来的问题。(当使用接口时,通常会有多重继承。)
和其他答案一样:
通过创建另一个覆盖这些虚拟方法的类来使用该接口。
class IDemo
{
public:
virtual void OverrideMe() = 0;
virtual ~IDemo() {}
}
要么
class IDemo
{
public:
virtual void OverrideMe() = 0;
protected:
~IDemo() {}
}
和
class Child : virtual public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
}
在C ++ 11中,您可以轻松地完全避免继承:
struct Interface {
explicit Interface(SomeType& other)
: foo([=](){ return other.my_foo(); }),
bar([=](){ return other.my_bar(); }), /*...*/ {}
explicit Interface(SomeOtherType& other)
: foo([=](){ return other.some_foo(); }),
bar([=](){ return other.some_bar(); }), /*...*/ {}
// you can add more types here...
// or use a generic constructor:
template<class T>
explicit Interface(T& other)
: foo([=](){ return other.foo(); }),
bar([=](){ return other.bar(); }), /*...*/ {}
const std::function<void(std::string)> foo;
const std::function<void(std::string)> bar;
// ...
};
在这种情况下,接口具有引用语义,即您必须确保对象的寿命超过接口(也可以使接口具有值语义)。
这些类型的接口各有利弊:
最后,继承是复杂软件设计中万恶之源。在肖恩·普伦特的《价值语义学和基于概念的多态性》(强烈建议在此处解释该技术的更好版本)中,研究了以下情况:
假设我有一个应用程序,在该应用程序中我可以使用该MyShape
接口多态处理形状:
struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle
在您的应用程序中,使用YourShape
接口对不同的形状执行相同的操作:
struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...
现在说您要使用我在您的应用程序中开发的某些形状。从概念上讲,我们的形状具有相同的界面,但是要使我的形状在您的应用程序中正常工作,您需要按以下方式扩展形状:
struct Circle : MyShape, YourShape {
void my_draw() { /*stays the same*/ };
void your_draw() { my_draw(); }
};
首先,完全不可能修改形状。此外,多重继承引领了通向意大利面条代码的道路(想象中第三个项目正在使用TheirShape
接口...如果他们还调用其draw函数会my_draw
怎样?)。
更新:关于基于非继承的多态性,有一些新的参考文献:
Circle
类设计不佳。Adapter
在这种情况下,您应该使用模式。抱歉,如果听起来有些刺耳,请尝试使用一些现实生活的库,例如Qt
在对继承进行判断之前。继承使生活更加轻松。
Adapter
模式固定Circle的示例(也许在ideone上)?我有兴趣看到它的优势。
Square
还不在那里?预知?这就是为什么它脱离现实。实际上,如果您选择依赖“ MyShape”库,则可以从一开始就采用其接口。在shapes示例中,有很多废话(其中之一是您有两个Circle
结构),但是适配器看起来像这样-> ideone.com/UogjWk
以上所有好的答案。您应该记住的另一件事-您也可以拥有一个纯虚拟析构函数。唯一的区别是您仍然需要实现它。
困惑?
--- header file ----
class foo {
public:
foo() {;}
virtual ~foo() = 0;
virtual bool overrideMe() {return false;}
};
---- source ----
foo::~foo()
{
}
您想要这样做的主要原因是,如果您想像我一样提供接口方法,但是将其覆盖设置为可选的。
要使该类成为接口类,需要一个纯虚拟方法,但是您所有的虚拟方法都具有默认实现,因此使纯虚拟成为唯一的方法是析构函数。
在派生类中重新实现析构函数一点都不重要-我总是在派生类中重新实现一个析构函数,无论是否虚拟。
如果您使用的是Microsoft的C ++编译器,则可以执行以下操作:
struct __declspec(novtable) IFoo
{
virtual void Bar() = 0;
};
class Child : public IFoo
{
public:
virtual void Bar() override { /* Do Something */ }
}
我喜欢这种方法,因为它可以使接口代码更小,并且生成的代码大小可以大大缩小。使用novtable会删除该类中对vtable指针的所有引用,因此您永远无法直接实例化它。请参阅此处的文档-novtable。
novtable
过的标准virtual void Bar() = 0;
= 0;
我添加的缺失)。如果您不理解,请阅读文档。
= 0;
并认为这只是一种完全相同的非标准方式。
除了这里写的内容外,还有一点补充:
首先,请确保您的析构函数也是纯虚拟的
其次,您可能想在实施时虚拟地(而不是正常地)继承,只是为了获得良好的效果。
您还可以考虑使用NVI(非虚拟接口模式)实现的合同类。例如:
struct Contract1 : boost::noncopyable
{
virtual ~Contract1();
void f(Parameters p) {
assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
// + class invariants.
do_f(p);
// Check post-conditions + class invariants.
}
private:
virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
virtual void do_f(Parameters p); // From contract 1.
virtual void do_g(Parameters p); // From contract 2.
};
我还是C ++开发的新手。我从Visual Studio(VS)开始。
但是,似乎没有人提到__interface
VS (.NET)。我不是很肯定,如果这是一个很好的方法来声明一个接口。但这似乎提供了额外的强制执行(在文档中提到)。这样您不必显式指定virtual TYPE Method() = 0;
,因为它将被自动转换。
__interface IMyInterface {
HRESULT CommitX();
HRESULT get_X(BSTR* pbstrName);
};
但是,我不使用它,因为我担心跨平台编译的兼容性,因为它仅在.NET下可用。
如果有人对它感兴趣,请分享。:-)
谢谢。
虽然确实virtual
是定义接口的事实上的标准,但不要忘了经典的类似于C的模式,该模式随C ++的构造函数一起提供:
struct IButton
{
void (*click)(); // might be std::function(void()) if you prefer
IButton( void (*click_)() )
: click(click_)
{
}
};
// call as:
// (button.*click)();
这样的好处是您可以重新绑定事件运行时,而不必再次构造您的类(因为C ++没有更改多态类型的语法,这是变色龙类的解决方法)。
提示:
click
后代的构造函数。protected
成员并具有public
引用和/或getter。if
代码中s与状态更改的次数,这可能比switch()
es或if
s 快(预计周转时间约为3-4 if
s,但始终要先进行测量。std::function<>
了函数指针,你也许能中管理所有对象数据IBase
。从这一点上,您可以为之准备有价值的原理图IBase
(例如,std::vector<IBase>
将起作用)。注意,这可能会慢一些,具体取决于您的编译器和STL代码。同样,std::function<>
与函数指针甚至虚拟函数相比,的当前实现往往会产生开销(将来可能会改变)。class Shape
{
public:
// pure virtual function providing interface framework.
virtual int getArea() = 0;
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
class Triangle: public Shape
{
public:
int getArea()
{
return (width * height)/2;
}
};
int main(void)
{
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
cout << "Rectangle area: " << Rect.getArea() << endl;
Tri.setWidth(5);
Tri.setHeight(7);
cout << "Triangle area: " << Tri.getArea() << endl;
return 0;
}
结果:矩形区域:35三角形区域:17
我们已经看到抽象类如何根据getArea()定义接口,另外两个类实现了相同的功能,但是使用了不同的算法来计算特定于形状的面积。