【问题标题】:What happens when we combine RAII and GOTO?当我们结合 RAII 和 GOTO 时会发生什么?
【发布时间】:2010-03-09 04:56:34
【问题描述】:

我想知道,除了纯粹出于好奇(因为没有人应该编写这样的代码!),我想知道 RAII 的行为如何与 goto(不是很可爱)。

class Two
{
public:
    ~Two()
    {
        printf("2,");
    }
};

class Ghost
{
public:
    ~Ghost()
    {
        printf(" BOO! ");
    }
};

void foo()
{
    {
        Two t;
        printf("1,");
        goto JUMP;
    }
    Ghost g;
JUMP:
    printf("3");
}

int main()
{
        foo();
}

在 Visual Studio 2005 中运行以下代码时,我得到以下输出。

1,2,3 BOO!

但是我想像、猜到、希望 'BOO!' 实际上不会出现,因为 Ghost 不应该被实例化(恕我直言,因为我没有知道这段代码的实际预期行为)。

怎么了?


我刚刚意识到,如果我为 Ghost 实例化一个显式构造函数,则代码不会编译...

class Ghost
{
public:
    Ghost()
    {
        printf(" HAHAHA! ");
    }
    ~Ghost()
    {
        printf(" BOO! ");
    }
};

啊,谜团……

【问题讨论】:

  • 我相信这种行为是正确的。否则,你怎么能在 JUMP 之后引用变量 g?

标签: c++ goto raii


【解决方案1】:

标准明确地谈到了这一点——举个例子; 6.7/3“声明声明”(重点由我添加):

每次执行声明语句时都会初始化具有自动存储持续时间的变量。 在块中声明的具有自动存储持续时间的变量在退出块时被销毁

可以转移到块中,但不能通过初始化绕过声明。除非变量具有 POD 类型并且在没有初始化程序的情况下声明,否则从具有自动存储持续时间的局部变量不在范围内的点跳转到它在范围内的点的程序是格式错误的。

[示例:

void f()
{
    //...
    goto lx;  //ill-formed: jump into scope of a
    //...

ly:
    X a = 1;
    //...

lx:
    goto ly;  //OK, jump implies destructor
              //call for a, followed by construction
              //again immediately following label ly
}

——结束示例]

所以在我看来,MSVC 的行为不符合标准 - Ghost 不是 POD 类型,因此当 goto 语句被编码以跳过它时,编译器应该会发出错误。

我尝试过的其他几个编译器(GCC 和 Digital Mars)出现错误。 Comeau 发出警告(但公平地说,我为 Comeau 构建的脚本已将其配置为与 MSVC 高度兼容,因此它可能是有意效仿微软的做法)。

【讨论】:

  • 感谢您找到标准中定义的位置!但是我想知道格式错误是否意味着它应该或不应该编译......
  • “格式错误”表示程序不是“格式正确”。编译器只需要接受并“正确执行”格式良好的程序。也就是说,如果它是“格式错误的”,那就是错误的。
  • @Robert:我在这里添加了几句关于 MSVC 的行为,就像我最初应该做的那样。
  • @greyfade:但编译器通常会接受某些格式错误的程序,除非您将它们置于某种“严格”模式。例如,int main() { 1LL; } 程序格式不正确,但可能需要一些努力才能说服您的编译器告诉您这一点。但是,在这种情况下,如果行为(a)由 MSVC 定义,则只能将跳过初始化描述为 MSVC 扩展; (b) 可能有用。
  • 您的公平表白听起来像是 Comeau 正在做一些不符合标准的事情。但是,它会发出警告,因此它会满足标准对它的所有要求。
【解决方案2】:

Goto 没有放射性。通过 goto 离开与通过异常离开几乎没有区别。通过 goto 输入应该是为了方便,而不是语言的限制。不知道幽灵是否被构建是一个很好的理由不这样做。

在构造函数之前跳转。如果您想在某个对象已经构建后跳入,请将其包含在一个新的范围内或以其他方式自行解决其生命周期。

【讨论】:

    【解决方案3】:

    在这种情况下,我发现以下方法很有用。

    void foo()
    {
        {
            Two t;
            printf("1,");
            goto JUMP;
        }
    
        {
            Ghost g;
            // operations that use g.
        }
    
    // g is out of scope, so following JUMP is allowed.
    JUMP:
        printf("3");
    }
    

    在 foo() 函数中限制变量 g 的范围,将使 goto 跳转合法。现在,我们不会从 g 未初始化的地方跳转到 g 预期被初始化的地方。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-10
      • 2011-04-17
      • 1970-01-01
      • 2013-04-18
      • 2014-03-31
      • 2018-05-03
      相关资源
      最近更新 更多