【问题标题】:Doesn't putting a "virtual destructor inside an interface" make it, by definition, not an interface anymore?根据定义,将“虚拟析构函数放入接口”是否不再使其不再是接口?
【发布时间】:2011-10-21 11:20:26
【问题描述】:

这就是我所在的盒子。我想了解为什么在你的接口类中有一个“虚拟析构函数”很重要。如果你能坚持到最后,你就会明白为什么这些东西是用引号引起来的……我也想让所有的词汇都绝对正确。到目前为止,我的流程如下:

  1. 有时你有基类,有时你有从基类继承的派生类。

  2. 如果您有一个发现自己指向派生对象的基指针,并且您希望从该基指针指向派生对象进行的成员函数调用表现为如果它实际上是从派生对象调用的,那么你调用的成员函数最好在基类中声明为 virtual。

  3. 接口是具有纯虚函数的任何类。如果从这个接口类派生出一个新类,并实现了所有的纯虚函数,那么最终就可以创建派生类的实例了。

  4. 你永远不能有一个接口类的实例,但是你可以有一个指向接口类的实例。

  5. 如果你有一个指向接口类的指针,它实际上指向派生类的一个对象(实际上,我想如果#4 是正确的,它总是必须如此),如果你决定通过您的指针删除该对象,那么如果您没有“接口类中的虚拟析构函数”,那么您销毁派生对象的意图将仅作为销毁基对象(即接口类)的调用执行) 并且由于没有虚拟析构函数,因此事情永远不会到达实际调用派生对象的析构函数的地步——从而导致内存泄漏。

呸。好的,如果这听起来不错,请回答我的问题。像这样在界面中声明一个虚拟析构函数就足够了吗:

virtual ~iFace();

这对我来说看起来不对......所以如果你像这样将析构函数设为纯虚拟会发生什么:

virtual ~iFace() = 0;

既然它们只是声明,那么它们中的任何一个都算作“接口类中的虚拟析构函数”吗?你甚至可以有一个已声明但未定义的析构函数吗?只有当它是纯虚拟的我才会猜......

无论如何,所以回到标题问题...我真的要尽可能快...这是钱...如果您的“接口类中的虚拟析构函数”至少需要一个空定义如下:

virtual ~iFace() {};

那么该成员函数不是纯虚函数(不可能是因为您给了它一个定义),因此您的类不再是接口(它不包含纯虚成员函数) .

这意味着如果你为你的接口定义了一个虚拟析构函数,那么你就不再有一个接口(而只是一些抽象基类)。这只是语言的滥用吗?我明白发生了什么吗?

注意:所有这些都来自于问自己“什么是接口?”然后阅读这个问题的答案:How do you declare an interface in C++?

希望步行时间不会太长,但我决心完全理解这些概念及其相关词汇。

【问题讨论】:

  • 接口的概念对于激发语言扩展来说非常重要。查看此页面:msdn.microsoft.com/en-us/library/4bt10hsa%28VS.80%29.aspx
  • 哇,好多好答案!
  • C++ 中的接口只是概念。仅仅因为你有一些函数的实现,它并不能阻止它成为一个接口(它只是在数学意义上不是纯粹的)。现实世界是一个肮脏肮脏的地方,并非所有事物都能完美地融入数学概念。只是顺其自然。 PS。所有的类也都有一些与之相关的编译器定义的方法,所以没有一个是纯粹的。聚苯乙烯。我讨厌 Java 对纯接口的强制执行(有时它会使代码更简单地定义辅助方法)。
  • “实际上,只有纯虚函数的类通常称为接口。” -- Bjarne S. ...我想我错过了“经常”这个词...该死! www2.research.att.com/~bs/bs_faq2.html
  • 我是 .net 人而不是 C++ 人,但我认为定义接口的概念不是它只有纯虚函数,而是它没有成员字段。如果一个类有一个纯虚拟方法 SetBounds(int left, int top, int width, int height, int args_specified) 和非虚拟包装方法,如 SetWidth(int width) 可以适当地调用它,我不认为非-virtual 方法将使其不是“接口”。我知道 .net 接口需要扩展方法,但我认为这是平台限制。

标签: c++ interface terminology pure-virtual


【解决方案1】:

C++ 没有本机接口实体。接口被实现为常规类。

因此,使类成为 C++ 中的接口的原因并不是具有普遍一致性的东西。我个人认为一个类是一个接口,如果它没有数据成员,没有用户声明的构造函数,并且它的所有函数都是纯虚拟的 - 除了它的析构函数可能例外 - 并且它的所有基类,如果有的话,也是接口。如果一个类不能完全符合所有这些属性,我可能会将其称为“胖”接口(通常不是恭维!)。

如果您想通过指向基类(例如“接口”类)的指针删除动态分配的多态类,那么基类析构函数必须声明为virtual。这意味着它必须是用户声明的析构函数,而不是隐式声明的非virtual 析构函数。

一旦显式声明了析构函数,就必须为其提供实现。 (无论基类析构函数是否声明为纯虚拟、虚拟或非虚拟,当您销毁从它派生的任何类的实例时,将始终使用基类析构函数。)这纯粹是 C++ 语言的实现细节。这并不意味着您的基类不再是“接口”,如果您有一个接口类,那么析构函数的实现很可能在任何情况下都是空的 - 您没有成员或基类成员担心。

如果你的接口至少有一些纯虚函数,那么将析构函数标记为纯虚函数没有真正的好处,你的接口类已经是一个抽象类。派生类析构函数在技术上不会覆盖基类析构函数,因此您不需要派生类提供用户声明的析构函数或类似的东西。

将析构函数声明为纯虚函数也会剥夺您在类定义中提供内联析构函数定义的能力,尽管这是一个小细节。

【讨论】:

  • "没有构造函数" - 没有用户定义的构造函数;-p
【解决方案2】:

“接口是任何只有纯虚函数的类”

-- C++中的概念叫做抽象类。抽象类是具有至少一个纯虚函数的类。它并不要求它的所有成员函数都是纯虚拟的。你不能实例化任何抽象类。

"这意味着如果你为你的 接口,那么你不再有一个接口(但只是一些 抽象基类)。这只是语言的滥用吗?难道我 明白发生了什么吗?”

-- 相反,你必须为析构函数提供定义,即使它是纯虚拟的,因为析构函数在继承层次结构中总是以自下而上的方式调用。

标准 12.4:

析构函数可以声明为虚拟(10.3)或纯虚拟(10.4);如果在程序中创建了该类或任何派生类的任何对象,则应定义析构函数。

例子:

class A
{
public:
   // this is stil a pure virtual function
   // when there is a definition
   virtual ~A() = 0;
};

class B: public A
{};

int main()
{
   // fail to link due to missing definition of A::~A()
   B b;
}

【讨论】:

    【解决方案3】:

    为什么 Abstract class 析构函数应该是虚拟的并有定义?

    在指向派生类对象的多态基类指针上调用delete 并且基类没有虚拟析构函数会导致未定义行为

    所以你需要将多态基类的析构函数声明为virtual。一旦你明确地声明你的析构函数是虚拟的,你就需要为它提供一个定义。这是因为编译器默认为每个类生成(定义)一个析构函数,但如果您显式声明析构函数,则编译器不会这样做,而是让您为自己的析构函数提供定义。这是有道理的,因为编译器将显式声明视为您想要在析构函数中执行一些不平凡的操作(即使您不需要这样做)的指示,并且它通过强制您给您提供这样做的机会定义。


    神话 1:
    在 C++ 中有一种叫做 Interface 的东西。


    C++ 作为一种语言不提供Interface 您所指的Interface 在C++ 中称为Abstract classAbstract Classes 用于模拟 C++ 中 Interface 的行为。

    什么是抽象类?
    根据定义,抽象类应该有至少一个纯虚函数。


    误区 2:
    Abstract 类中的所有函数都必须是纯虚函数。


    Abstract classes 并不要求其中的所有函数都是纯虚函数。如果抽象对象至少具有一个纯虚函数,则不能创建该对象。不过,正如您正确提到的,您可以创建指向它的指针。


    误区 3:
    纯虚函数不能有定义。


    纯虚函数有一个定义是完全有效的。


    为什么我需要有定义的 Pure virtual function
    代码胜于雄辩,所以这里是一个简单的例子:
    警告:未编译的代码仅用于演示

    class IMyInterface
    {
        int i;
        int j;
        public:
            virtual void SetMembers(int ii, int jj)=0;
    };
    
    /*The pure virtual function cannot be inline in the class definition*/
    /*So this has to be here*/
    void IMyInterface::SetMembers(int ii, int jj)
    {
        i = ii;
        j = jj;
    }
    
    class Myclass: public IMyInterface
    {
        int k;
        int l;
        public:
            virtual void SetMembers(int ll, int m, int a, int b)
            {
                 k = ll;
                 l = m;
                 IMyInterface::SetMembers(a,b);
             }
    };
    
    int main()
    {
    
        MyClass obj;
        obj.SetMembers(10,20,30,40);
        return 0;
    }
    

    【讨论】:

    • 我只是补充一点,因为没有语法可以将主体添加到纯虚成员函数声明中,因此必须在类定义之外定义。
    • @Nicola Musatti:同意,我清楚地记得有一个 Language Lawyer Q 关于为什么纯虚函数不能有 Armen 的内联定义。
    • @Als:我知道 ABC 至少有一个虚函数......让我感到困惑的一件事是阅读“接口的等价物将是一个抽象基类,没有任何数据,只有纯虚函数”来自我引用的另一个问题。该死的。至于神话#3,为什么你会想要一个纯虚成员函数的定义?我不知道,但我相信这个问题的答案同样很棒:) 谢谢!
    • @Jimmy:检查更新的答案,希望这个简单的例子能回答您关于Myth #3 的查询:)
    • @Als:“这非常有用(为派生类提供一些简单的常见实现细节)。” -- Bjarne (www2.research.att.com/~bs/bs_faq2.html) 我无法决定我更喜欢什么,你用构造函数展示它的方式,或者使用“非常偶尔”这个短语......无论如何,你们正在扼杀它!
    【解决方案4】:
    1. 好的。
    2. 好的;如果成员函数未在基类中声明为 virtual,则调用基类中的成员函数;如果成员函数既没有在基类中定义也没有声明为纯虚函数,则会出现错误。
    3. 在 C++ 中没有 Java 和 C# 中的接口; C++ 中的抽象基类结合了接口和抽象类,因为它们存在于后两种语言中。如果一个 C++ 类至少有一个纯虚成员函数,那么它就是抽象类。
    4. 抽象类替换interface
    5. 如果基类的析构函数未声明为虚拟的,则从指向基类的指针中删除派生类时,您不能正式假设会发生什么。

    考虑到这一切,一般来说你的抽象基类已经有一些纯虚成员函数来确保它不可能实例化它,所以通常的做法是定义一个内联虚析构函数什么都没有。

    【讨论】:

      猜你喜欢
      • 2011-04-07
      • 2011-03-03
      • 1970-01-01
      • 2011-10-01
      • 2020-05-19
      • 2021-10-04
      • 1970-01-01
      • 2011-08-27
      • 2016-10-13
      相关资源
      最近更新 更多