C++析构函数:是否存在未定义行为?

3
考虑以下代码段:
#include<iostream>
#include<string>

class A
{
    private:
        char name[10];

    public:
        A() { }
        A(const char *str)
        {
            strcpy(name, str);
            std::cout<<name<<" constructed"<<endl; 
        }
        ~A()
        {
           std::cout<<name<<" destructed"<<endl;
        }
};

int main()
{
   A a("a");
   A b("b");
   return 0;
}

以下程序的输出结果为:
a constructed
b constructed
b destructed
a destructed

我对以上代码的唯一解释是,由于a之后创建了b,因此b应该在栈中存储在a之上。现在当主函数完成时,b先被弹出,然后是a,因此它的析构函数先被调用,然后是a的析构函数。
我的问题是:我这样想是正确的吗?还是以上情况是未定义行为,可能因编译器而异?

如果不是这样的话,如果'b'在这里存储了对'a'的引用,在b的析构函数中访问该'a'将会导致未定义的行为。 - mfontanini
3
我认为您将未定义行为和未指定行为混淆了。 - Luchian Grigore
1
它因此被称为“堆栈”。先进者,后出。 - juanchopanza
@juan 实际上,调用栈在概念上存储堆栈帧(或“激活记录”)。这些以LIFO方式处理,而不一定是单个堆栈帧的元素。 - Konrad Rudolph
@KonradRudolph 很有趣。您有例子说明堆栈帧的元素不按LIFO顺序处理是有意义的情况吗? - juanchopanza
@juan 不是的,因为生命周期限制仍然很重要(请参见mfontanini上面的评论)。我只是认为这是巧合,并与调用堆栈作为堆栈无关。或者说,它是一个不同的抽象层,尽管从处理器架构实现的角度来看当然是有意义的(这是一种非常有效的处理存储的方式)。 - Konrad Rudolph
2个回答

11

对象在自动内存(栈)中创建的顺序与销毁顺序相反,这在标准中得到了充分的说明。

C++03 15.2. 构造函数和析构函数

  1. [...] 自动对象的销毁顺序与它们的构造完成顺序相反。

破坏它们的逆序,有特定的原因吗?C++标准规定了这样做吗? - Ravi Gupta
@RaviGupta 是的,标准是这样规定的。 - Luchian Grigore
2
还可以想象这样一种情况,当您创建对象并将指针或引用传递给下一个对象时。如果销毁顺序未定义或与构造完全相同,则第二个对象将具有无效的指针或引用,这会使编程变得复杂。 - Greg
@greg:我明白你的意思,但为了更清楚地表达,请提供一个例子。 - Ravi Gupta
请注意,在表达式的求值过程中创建的临时对象遵循相同的规则:它们的创建可能不遵循任何特定顺序(函数参数的求值顺序未指定),但它们仍将按照相反的构造顺序被销毁。 - Matthieu M.

3
以下是需要翻译的内容:

为什么销毁顺序很重要(应该与创建顺序相反)

class Foo
{
public:
  void foo() { /* ... */ }
};

class Bar
{
public:
   Bar(Foo const & foo) foo(foo) {}
   virtual ~Bar() { this->foo.foo(); }

   Foo const & foo;
};

int main()
{
  Foo foo;
  Bar bar(foo);
  // if foo gets destroyed before bar, then bar will call method foo() on invalid reference in its destructor
  // it is much more convenient to have bar destroyed before foo in such cases
}

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