C++中的抽象类与接口的区别

117
这是一个关于C++的一般性问题。正如你所知,在C++中,与Java和C#不同,没有明确区分“接口”和“抽象类”。在什么情况下使用“接口”而不是“抽象类”会更好呢?你能给出一些例子吗?该怎样声明一个C++中的接口?(参考链接:How do you declare an interface in C++?

6
由于在 C++ 中没有明确的界面概念,因此您需要定义自己对“界面”所指的含义。(如果没有明确的区别,则优先选择其中一个是没有意义的。) - CB Bailey
5
正如你所说,如果你不定义什么是接口以及它与抽象类的区别,那么这个问题就没有意义。 - juanchopanza
1
C++语言中没有接口,你只能通过抽象类来模拟它们。 - Dmytro Sirenko
14
虽然这个问题的措辞肯定可以更好,但要这样做需要提问者已经知道答案。我很讨厌在SO上经常看到一堆聪明的臭虫。在寻找何时或如何回答之前,你需要清楚为什么要这样做。 "接口"体现了客户端和实现之间合同的概念。 "抽象类"包含您想要在多个接口实现之间共享的代码。虽然在抽象类的方法中隐含了接口,但有时单独指定合同是有用的。 - Dave
正如前面的答案所解释的,在C++中可以使用只有纯虚方法的类来实现这一点。对于之前学习过Java或C#的开发人员来说,这可能看起来有些奇怪。它似乎是样板代码过多,但语言维护者已经决定,特别处理“接口”的任何好处都被潜在的不一致性和额外的语言复杂性所抵消。C++实际上不需要任何额外的复杂性。 - Dave
显示剩余3条评论
5个回答

164
我假设你所说的“接口”是一个只含有纯虚方法(即没有任何代码)的C++类,而当提到“抽象类”时,指的是一个具有可以被覆盖的虚方法及部分代码但至少有一个纯虚方法使得该类无法实例化。例如:
class MyInterface
{
public:
  // Empty virtual destructor for proper cleanup
  virtual ~MyInterface() {}

  virtual void Method1() = 0;
  virtual void Method2() = 0;
};


class MyAbstractClass
{
public:
  virtual ~MyAbstractClass();

  virtual void Method1();
  virtual void Method2();
  void Method3();

  virtual void Method4() = 0; // make MyAbstractClass not instantiable
};
在Windows编程中,接口对于COM是至关重要的。实际上,COM组件只导出接口(即指向v表的指针,即一组函数指针的指针)。这有助于定义一个ABI(应用程序二进制接口),使得可以在C++中构建COM组件并在Visual Basic中使用它,在C中构建COM组件并在C++中使用它,或使用Visual C++版本X构建COM组件并在版本Y中使用它。
换句话说,使用接口可以在客户端代码和服务器端代码之间实现高度解耦。
此外,如果想要构建具有C++面向对象接口的DLL(而不是纯C DLL),如此文章中所述,最好导出接口("成熟方法")而不是C++类(这基本上是COM所做的,但没有COM基础设施的负担)。
如果我想定义一组规则以编写组件,而不指定具体的行为,那么我会使用接口。实现该接口的类将提供一些具体的行为。
相反,当我想要提供一些默认的基础结构代码和行为,并使客户端代码从这个抽象类派生,覆盖纯虚方法并使用自定义代码完善此行为时,我会使用抽象类。例如,想像一个OpenGL应用程序的基础结构。可以定义一个抽象类来初始化OpenGL、设置窗口环境等,然后可以从这个类派生并实现自定义代码以处理呈现过程和处理用户输入。
// Abstract class for an OpenGL app.
// Creates rendering window, initializes OpenGL; 
// client code must derive from it 
// and implement rendering and user input.
class OpenGLApp
{
public:
  OpenGLApp();
  virtual ~OpenGLApp();
  ...

  // Run the app    
  void Run();


  // <---- This behavior must be implemented by the client ---->

  // Rendering
  virtual void Render() = 0;

  // Handle user input
  // (returns false to quit, true to continue looping)
  virtual bool HandleInput() = 0;

  // <--------------------------------------------------------->


private:
  //
  // Some infrastructure code
  //
  ... 
  void CreateRenderingWindow();
  void CreateOpenGLContext();
  void SwapBuffers();
};


class MyOpenGLDemo : public OpenGLApp
{
public:
  MyOpenGLDemo();
  virtual ~MyOpenGLDemo();

  // Rendering
  virtual void Render();  // implements rendering code

  // Handle user input
  virtual bool HandleInput(); // implements user input handling


  //  ... some other stuff
};

8
@AdrianMaire 我的回答是技术性的;我不会为任何人做_“广告”_。 - Mr.C64
“// make MyAbstractClass not instantiable” 这个注释让我感到困惑。难道不是因为 void Method3(); 的声明使得这个类成为抽象类,因为它没有被声明为虚函数吗? - Celeritas
Method3只是一个普通方法,在代码中必须定义在某个位置。 - user2913685
@Celeritas 一个类成为抽象类的条件是它至少有一个纯虚函数。 - Wadite RK

35

interface最初是由Java流行起来的。
以下是interface及其C ++等效项的性质:

  1. interface只能包含无内容的抽象方法; C++等效项是纯虚方法,但它们可以/不能有内容。
  2. interface只能包含static final数据成员; C++ 等效项是静态常量数据成员,这些成员是编译时常数。
  3. 一个Java class可以实现多个interface,因为需要这种功能,因为Javaclass只能继承1个class; C++支持使用virtual关键字直接进行多重继承。

由于第3点,interface概念从未在C ++中正式引入。尽管如此,仍然可以具有灵活性。

此外,您可以参考Bjarne关于此主题的FAQ


6
基本上,在 C++ 中,不存在像 Java 和C# 中强制实现“接口化”行为的语言元素。但是由于“接口”主要是一个概念,您可以利用您提到的语言特性创建接口。 - Dzyann
"C++的等效方法是纯虚函数,尽管它们可以/不能有主体" - 根据定义,基类中的纯虚函数没有主体,并且在派生类中必须有一个主体。此外,在C++中,您不需要使用virtual关键字进行多重继承。实际上,良好的设计使用多重继承避免使用virtual关键字(如果可能的话,他们会尝试完全避免多重继承)。 - Samaursa
“类外定义”是什么意思?根据定义,纯虚函数没有函数体,并且派生类必须重写它们,除非它们被进一步派生,在这种情况下它们也是抽象的:http://ideone.com/hc1Zq8。 - Samaursa
5
@Samaursa,这是一个如何定义纯虚方法的函数体的示例。 - iammilind
有趣的事情:对于纯虚析构函数,你仍然应该有一个(空的)函数体。 - 3Dave
@David,这是因为如果你声明一个对象或者使用new创建一个指针,析构函数总是会被调用(由编译器隐式放置)。因此对于纯虚析构函数,必须定义一个函数体,除非没有创建对象。在C++03中,所有方法都是静态的class是一个很好的使用案例,其中不需要定义纯虚析构函数的函数体。但是在C++11中可以通过使用= delete来实现。 - iammilind

16

当需要一些共同的实现时,可以使用抽象类。如果您只想指定程序的某些部分必须遵守的契约,则可以使用接口。通过实现接口,您保证将实现某些方法。通过扩展抽象类,您继承了其中的一些实现。因此,接口只是一个没有实现任何方法(都是纯虚拟的)的抽象类。


2
“接口只是一个没有实现方法的抽象类” - 并且没有非静态数据成员,这是您所期望的。 - Steve Jessop
我希望即使是纯虚函数,析构函数也能够被实现。 - CB Bailey
是的,我的意思是“没有实现任何功能”。 - Will

6

纯虚函数主要用于定义:

a) 抽象类

这些是基类,您必须从它们派生并实现纯虚函数。

b) 接口

这些是“空”的类,其中所有函数都是纯虚函数,因此您必须派生并实现所有函数。

纯虚函数实际上是在基类中没有实现的函数,并且必须在派生类中实现。


-1
请不要将成员放入接口中,尽管在措辞上是正确的。请不要“删除”接口。
class IInterface() 
{ 
   Public: 
   Virtual ~IInterface(){}; 
   … 
} 

Class ClassImpl : public IInterface 
{ 
    … 
} 

Int main() 
{ 

  IInterface* pInterface = new ClassImpl(); 
  … 
  delete pInterface; // Wrong in OO Programming, correct in C++.
}

11
为什么不应该删除接口?在这个例子中,接口有一个虚析构函数。所以,即使我们通过IInterface指针删除对象,在这种情况下也会调用ClassImpl的析构函数,是吗? - Ferit Buyukkececi
1
使用C++11及其以上版本,没有必要显式地使用new/delete。应该使用std::make_uniquestd::make_shared代替。这将防止内存泄漏和其他常见错误。 - Flip

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接