【问题标题】:Local variable scope question局部变量范围问题
【发布时间】:2010-11-26 04:45:01
【问题描述】:

为什么下面的代码会打印“xxY”?局部变量不应该存在于整个函数的范围内吗?我可以使用这种行为吗?或者这将在未来的 C++ 标准中改变?

我认为根据 C++ 标准 3.3.2 “在块中声明的名称是该块的本地名称。它的潜在范围从其声明点开始,并在其声明区域的末尾结束。 em>"

#include <iostream>
using namespace std;

class MyClass
{
public:
  MyClass( int ) { cout << "x" << endl; };
  ~MyClass() { cout << "x" << endl; };
};

int main(int argc,char* argv[])
{
  MyClass  (12345);
// changing it to the following will change the behavior
//MyClass m(12345);
  cout << "Y" << endl;

  return 0;
}

根据回复,我可以假设 MyClass(12345); 是表达式(和范围)。这是有道理的。所以我希望下面的代码总是打印“xYx”:

MyClass (12345), cout << "Y" << endl;

并且允许进行这样的替换:

// this much strings with explicit scope
{
  boost::scoped_lock lock(my_mutex);
  int x = some_func(); // should be protected in multi-threaded program
} 
// mutex released here

//    

// I can replace with the following one string:
int x = boost::scoped_lock (my_mutex), some_func(); // still multi-thread safe
// mutex released here

【问题讨论】:

  • 您的问题包含答案:已声明的名称...。没有名字!
  • 在此示例中:MyClass(12345) 是函数样式转换,而不是声明。
  • 仍然没有实例名称

标签: c++ scope raii


【解决方案1】:

您实际上是在创建一个对象而没有将其保留在范围内,因此它在创建后立即被销毁。因此,您正在经历的行为。

您无法访问创建的对象,那么编译器为什么要保留它?

【讨论】:

    【解决方案2】:

    在你的

    中创建的对象
    MyClass(12345);
    

    是一个临时对象,它只在那个表达式中存在

    MyClass m(12345);
    

    是一个在整个区块中都处于活动状态的对象。

    【讨论】:

    • 那个表达式不是主要功能吗?
    • 这对我来说似乎是正确的。另一件事可能是优化:即使您确实使用了第二种方法,编译器也可能将其优化为第一种。
    • @Anna,在第二种情况下,它总是会打印 xYx。
    • @Kirill:该表达式在main() 的语句中。这不是main()的全部。
    • @Anna:没错。规范谈论“潜在范围”,因此不强制执行范围。编译器可以为所欲为(包括提前销毁对象)
    【解决方案3】:

    您正确引用了标准。让我强调一下:

    块中的名称 声明是该块本地的。它的潜在作用域从它的声明点开始,到它的声明区域结束。

    实际上,您没有声明任何 name。你的线路

    MyClass (12345);
    

    甚至不包含声明!它包含一个创建 MyClass 实例的表达式,计算表达式(但是,在这种特殊情况下,没有要计算的内容),并将其结果转换为 void,并销毁在那里创建的对象。

    听起来不那么令人困惑的事情

    call_a_function(MyClass(12345));
    

    你看过很多次并且知道它是如何工作的,不是吗?

    【讨论】:

    • 规范还谈到了“潜在”范围。不是“保证”。所以编译器可以提前销毁没有在作用域内使用的对象。
    • “潜在作用域”有一个精确的含义:它是作用域加上由于重新声明而隐藏名称的部分。即使不再使用对象,实现也不能提前销毁对象(这会破坏 RAII 的一些主要用途)。
    【解决方案4】:

    回答您的其他问题。以下是逗号运算符的调用。它创建了一个MyClass 临时对象,其中包括调用其构造函数。然后它计算第二个表达式cout &lt;&lt; "Y" &lt;&lt; endl,它将打印出 Y。然后,在完整表达式的末尾,它将销毁临时变量,该临时变量将调用其析构函数。所以你的期望是正确的。

    MyClass (12345), cout << "Y" << endl;
    

    要使以下内容起作用,您应该添加括号,因为逗号在声明中具有预定义的含义。它将开始声明一个函数some_func,返回一个int,不带任何参数,并将scoped_lock对象分配给x。使用括号,您说整个事情是一个逗号运算符表达式。

    int x = (boost::scoped_lock (my_mutex), some_func()); // still multi-thread safe
    

    需要注意的是下面两行是等价的。第一个使用my_mutex作为构造函数参数创建一个临时的未命名对象,但名称周围的括号是多余的。不要让语法迷惑你。

    boost::scoped_lock(my_mutex);
    boost::scoped_lock my_mutex;
    

    我看到过滥用范围和生命周期这两个术语。

    • Scope 是您可以在不限定名称的情况下引用名称的地方。名称具有范围,对象继承用于定义它们的名称的范围(因此标准有时会说“本地对象”)。临时对象没有范围,因为它没有名称。同样,new 创建的对象也没有作用域。范围是编译时属性。该术语在标准中经常被误用,请参阅this defect report,因此很难找到真正的含义。

    • Lifetime 是运行时属性。这意味着当对象设置并准备好使用时。对于类类型对象,生命周期在构造函数结束执行时开始,在析构函数开始执行时结束。生命周期经常与范围混淆,尽管这两件事完全不同。

      临时对象的生命周期是精确定义的。它们中的大多数在评估它们所包含的完整表达式(如上面的逗号运算符或赋值表达式)后结束生命周期。临时对象可以绑定到 const 引用,这将延长它们的生命周期。抛出异常的对象也是临时对象,当不再有处理程序时,它们的生命周期就结束了。

    【讨论】:

      猜你喜欢
      • 2020-02-29
      • 2014-11-11
      • 1970-01-01
      • 2014-03-02
      • 1970-01-01
      • 2013-10-14
      • 2014-09-17
      • 1970-01-01
      • 2013-12-25
      相关资源
      最近更新 更多