如何设置代表接口的类?这只是一个抽象基类吗?
要扩展 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 的类方法。
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Child : public IDemo
{
public:
virtual void OverrideMe()
{
// do stuff
}
};
override
除了 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 类型的对象,请在您的接口中声明一个虚拟析构函数或创建一个受保护的非虚拟析构函数以避免未定义的行为。使用虚拟继承来避免多重继承的问题。 (当我们使用接口时,更常见的是多重继承。)
和其他答案一样:
使用纯虚方法创建一个类。
通过创建另一个覆盖这些虚拟方法的类来使用该接口。类 IDemo { public: virtual void OverrideMe() = 0; virtual ~IDemo() {} } 或 class IDemo { public: virtual void OverrideMe() = 0; protected: ~IDemo() {} } And 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;
// ...
};
在这种情况下,接口具有引用语义,即您必须确保对象的寿命比接口长(也可以使用值语义制作接口)。
这些类型的接口各有利弊:
它们比基于继承的多态性需要更多的内存。
它们通常比基于继承的多态性更快。
在您知道最终类型的情况下,它们要快得多! (一些编译器如 gcc 和 clang 对不具有/继承自具有虚函数的类型的类型执行更多优化)。
最后,继承是复杂软件设计中万恶之源。在 Sean Parent's Value Semantics and Concepts-based Polymorphism 中(强烈推荐,该技术的更好版本在那里解释)研究了以下案例:
假设我有一个应用程序,我在其中使用 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
接口......如果他们也调用他们的绘图函数 my_draw
会发生什么?)。
更新:有一些关于基于非继承的多态性的新参考:
Sean Parent's Inheritance 是恶语的基类。
Sean Parent 的价值语义和基于概念的多态性演讲。
Pyry Jahkola 的无继承多态性演讲和 poly 库文档。
Zach Laine 的实用类型擦除:用优雅的设计模式演讲解决 OOP 问题。
Andrzej 的 C++ 博客 - 类型擦除部分 i、ii、iii 和 iv。
运行时多态泛型编程——在 ConceptC++ 中混合对象和概念
Boost.TypeErasure 文档
Adobe Poly 文档
Boost.Any,std::any 提案(修订版 3),Boost.Spirit::hold_any。
Circle
类设计不佳。在这种情况下,您应该使用 Adapter
模式。对不起,如果这听起来有点刺耳,但在判断继承之前尝试使用一些现实生活中的库,如 Qt
。继承让生活更轻松。
Adapter
模式修复 Circle 的示例(可能在 ideone 上)?我有兴趣看到它的优点。
上面所有的好答案。您应该记住的另一件事 - 您还可以拥有一个纯虚拟析构函数。唯一的区别是您仍然需要实现它。
使困惑?
--- header file ----
class foo {
public:
foo() {;}
virtual ~foo() = 0;
virtual bool overrideMe() {return false;}
};
---- source ----
foo::~foo()
{
}
你想要这样做的主要原因是如果你想提供接口方法,就像我一样,但要覆盖它们是可选的。
要使类成为接口类,需要纯虚方法,但所有虚方法都有默认实现,因此唯一可以制作纯虚的方法是析构函数。
在派生类中重新实现析构函数没什么大不了的——我总是在派生类中重新实现析构函数,无论是否虚拟。
您还可以考虑使用 NVI(非虚拟接口模式)实现的合同类。例如:
struct Contract1 : boost::noncopyable
{
virtual ~Contract1() = default;
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:
void do_f(Parameters p) override; // From contract 1.
void do_g(Parameters p) override; // From contract 2.
};
如果您使用的是 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;
的情况下阅读了它,并认为这只是一种非标准的完全相同的方式。
对上面写的内容做一点补充:
首先,确保你的析构函数也是纯虚拟的
其次,您可能希望在实施时虚拟(而不是通常)继承,只是为了采取良好的措施。
在 C++20 中,您可以使用 concept
代替类。它比继承更有效。
template <class T>
concept MyInterface = requires (T t) {
{ t.interfaceMethod() };
};
class Implementation {
public:
void interfaceMethod();
};
static_assert(MyInterface<Implementation>);
然后你可以在一个函数中使用它:
void myFunction(MyInterface auto& arg);
限制是您不能在容器中使用它。
虽然 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++ 没有用于更改多态类型的语法,这是变色龙类的解决方法)。
提示:
您可以从 this 作为基类继承(允许虚拟和非虚拟)并在后代的构造函数中填写 click 。
您可能将函数指针作为受保护的成员并具有公共引用和/或 getter。
如上所述,这允许您在运行时切换实现。因此,它也是一种管理状态的方法。根据代码中 ifs 与状态更改的数量,这可能比 switch()es 或 ifs 更快(周转预计在 3-4 个 ifs 左右,但始终先测量。
如果您选择 std::function<> 而不是函数指针,您可能能够在 IBase 中管理所有对象数据。从这一点开始,您可以获得 IBase 的值原理图(例如,std::vector
我还是 C++ 开发的新手。我从 Visual Studio (VS) 开始。
然而,似乎没有人提到 VS (.NET) 中的 __interface
。我不是很确定这是否是声明接口的好方法。但它似乎提供了额外的强制执行(在 the documents 中提到)。这样您就不必明确指定 virtual TYPE Method() = 0;
,因为它会自动转换。
__interface IMyInterface {
HRESULT CommitX();
HRESULT get_X(BSTR* pbstrName);
};
但是,我不使用它,因为我担心跨平台编译兼容性,因为它仅在 .NET 下可用。
如果有人对此有任何有趣的事情,请分享。 :-)
谢谢。
如果您只想要接口的静态绑定(没有虚拟,没有接口类型本身的实例,接口仅作为指导):
#include <iostream>
#include <string>
// Static binding interface
// Notice: instantiation of this interface should be usefuless and forbidden.
class IBase {
protected:
IBase() = default;
~IBase() = default;
public:
// Methods that must be implemented by the derived class
void behaviorA();
void behaviorB();
void behaviorC() {
std::cout << "This is an interface default implementation of bC().\n";
};
};
class CCom : public IBase {
std::string name_;
public:
void behaviorA() { std::cout << "CCom bA called.\n"; };
};
class CDept : public IBase {
int ele_;
public:
void behaviorB() { std::cout << "CDept bB called.\n"; };
void behaviorC() {
// Overwrite the interface default implementation
std::cout << "CDept bC called.\n";
IBase::behaviorC();
};
};
int main(void) {
// Forbid the instantiation of the interface type itself.
// GCC error: ‘constexpr IBase::IBase()’ is protected within this context
// IBase o;
CCom acom;
// If you want to use these interface methods, you need to implement them in
// your derived class. This is controled by the interface definition.
acom.behaviorA();
// ld: undefined reference to `IBase::behaviorB()'
// acom.behaviorB();
acom.behaviorC();
CDept adept;
// adept.behaviorA();
adept.behaviorB();
adept.behaviorC();
// adept.IBase::behaviorC();
}
这是 c++ 标准中 abstract class
的定义
n4687
13.4.2
抽象类是只能用作其他类的基类的类;抽象类的任何对象都不能被创建,除非是从它派生的类的子对象。如果一个类至少有一个纯虚函数,那么它就是抽象的。
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() 定义一个接口,而另外两个类实现了相同的功能,但使用不同的算法来计算特定于形状的区域。
不定期副业成功案例分享
=0
) 析构函数。这里的优点是编译器理论上可以看到 vtable 现在没有有效成员,并完全丢弃它。使用带有主体的虚拟析构函数,可以(虚拟)调用所述析构函数,例如在构造中间通过this
指针(当构造的对象仍然是Parent
类型时),因此编译器必须提供有效的 vtable .因此,如果您在构造过程中没有通过this
显式调用虚拟析构函数 :) 您可以节省代码大小。override
关键字以允许编译时参数和返回值类型检查。例如,在 Childvirtual void OverrideMe() override;
的声明中