【问题标题】:Inheritance and destructors - theoretical question - C++继承和析构函数 - 理论问题 - C++
【发布时间】:2011-11-12 02:53:51
【问题描述】:
class A
{
    public:
         virtual void f(){ printf("A.f "); }
         ~A(){ f(); }
};

class B : public A
{
    A a;

    public:
         void f(){ printf("B.f "); }
         B(){ throw -1; }
        ~B(){ f(); }
};

int main()
{
    try{ B b; }
    catch(...){ printf("Exc");}
}

这就是我的看法。在 try 块内,构造 B b; 时没有打印任何内容。块结束。我认为编译器首先破坏了A a; 成员。所以A.f() 会被打印出来。这是否意味着class B 实例的销毁已经完成?之后,编译器会简单地调用~A()(破坏基类)吗?

我想我应该得到A.f(),然后是B.f()(破坏B类实例),然后又是A.f()(基类的析构函数)。编译这个让我思考了一下。 Exc 将在课程结束时打印。 我已经浏览了几个主题,但没有找到任何东西。

编辑:Dev-C++ (GCC 3.4.2) 的输出是

A.f A.f Exc

【问题讨论】:

  • 如果你的输出不是你预期的那样有问题,你需要发布你得到的实际输出。
  • 我得到:A.f A.f Exc (gcc 4.4)
  • 我将把它留在这里:dev-c++.
  • @R.: Dev-C++ 的相关性是什么?
  • @Tomalak:问题中提到了。

标签: c++ exception inheritance destructor


【解决方案1】:

这里确实有两个A 对象。

  1. B 继承自 A,因此 A 的基类对象在 B 之前首先被实例化。
  2. 另一个A 实例被创建,因为您有一个A 类型的成员字段作为B 的一部分。

当您创建B b 时,您将创建基类A,以及实例A a

但是,您随后在 B 的构造函数中抛出异常,因此此时所有完全构造的对象都被破坏,也就是说。

  • 在实例A a 上调用~A()
  • ~A() 在基类 A 上调用。

这就解释了为什么你会得到A.f A.f Exc

B 的析构函数不会被调用,因为 B 的构造函数没有成功完成,因此没有完全构造。

【讨论】:

  • +1 不错的答案,简明扼要。整个虚拟事物都是一条红鲱鱼。
  • @Tomalak - 抱歉,我在编辑之前输入了这个。不过,您的 C++ 标准引用要完整得多。
  • 谢谢大家,不知道throw是终止构造。
  • @Tomalak:毫无疑问,您得到了正确的答案,但有时简短而直接的答案更有启发性。仅仅说“这是因为你破坏了成员对象和子对象”可能比引用标准的含义更容易理解。
  • 您的销毁顺序错误。先销毁成员,然后销毁基类。
【解决方案2】:

你没有向我们展示你得到的输出,只是一堵乱七八糟的文字墙,所以很难知道你在问什么。

但是,为了记录,the output of your code is

A.f A.f Exc


为什么?

  • 构造 b 失败。
  • bB 析构函数未被调用,但其成员的析构函数为1
  • 它有一个A类型的成员,其析构函数调用函数f()
  • 还有一个完全构造的A基础b;所以,bA 析构函数也被调用,像以前一样调用A::f()
  • Exc 当然是由周围的异常处理程序输出的。

这是你想知道的吗?


1

[n3290: 15.2/2]: 任何存储持续时间的对象,其 初始化或销毁被异常终止会有 为其所有完全构造的子对象执行析构函数 (不包括类联合类的变体成员),也就是说,对于 主构造函数 (12.6.2) 已完成的子对象 执行和析构函数尚未开始执行。 [..]

【讨论】:

  • 您可能想删除此答案。 B 的析构函数没有被调用。
  • @Alf:那么至少删除你的评论吧!
  • 一个小问题:你的第二点是一个本体错误:“b的析构函数”甚至不存在,因为没有对象b。由于构造函数没有完成,所以没有对象,所以首先没有要销毁的对象。这就是为什么虚函数也是一个红鲱鱼。
  • @Kerrek:呵呵,可以说。除了b 确实存在,并且它的运行时类型为A。但它仍然有一个静态类型 [unconstructed] B,所以我认为它仍然“有”一个析构函数。不过,这类东西并没有真正定义。
【解决方案3】:

顺序应该是: A.f, A.f, Exc

在调用B的构造函数时,在进入之前,由于继承,首先调用了A的构造函数。接下来,在进入B的构造函数之前(即{之前),a是默认构造的。

B 的构造只有在达到匹配} 时才会完成。但在此之前,您有一个 throw 声明。所以部分构造的B必须被销毁,它有一个对象a和继承的子对象A。所以这两个都被销毁了,因此A.f和A.f

接下来,您到达打印“Exc”的抛出块

【讨论】:

    【解决方案4】:
    #include <stdio.h>
    
    class A
    {
        public:
             virtual void f(int i){ printf("A.f %i\n", i); }
             ~A(){ f(0); }
    };
    
    class B : public A
    {
        A a;
    
        public:
             void f(int i){ printf("B.f %i\n", i); }
             B(){ throw -1; }
             ~B(){ f(1); }
    };
    
    int main()
    {
        try{ B b; }
        catch(...){ printf("Exc\n");}
    }
    

    A 的析构函数被调用了两次,就是这样。

    输出:

    A.f 0
    A.f 0
    Exc
    

    【讨论】:

    • 这没什么好说的,stdio.h 在 C++ 中已被弃用。
    • 1.原作者使用了printf,我想尽量少改动代码。
    【解决方案5】:

    您不能从构造函数或析构函数调用虚函数。它们不会像虚拟一样工作,但会被称为非虚拟函数。
    您可以在 FAQ here 和有关构造函数的相关主题 here 中阅读它。

    【讨论】:

    • 这与手头的问题无关。不过,这是一个值得注意的声明,可以作为评论。
    猜你喜欢
    • 2012-11-14
    • 2021-06-24
    • 1970-01-01
    • 2012-04-28
    • 1970-01-01
    • 2011-12-31
    • 2011-10-14
    • 2020-07-17
    • 1970-01-01
    相关资源
    最近更新 更多