本地变量作用域问题

8
为什么以下代码会打印“xxY”?局部变量不应该存在于整个函数的作用域中吗?我能利用这种行为还是将来的C++标准会改变这一点?
我认为根据C++标准3.3.2,“在块中声明的名称只在该块中本地。其潜在范围始于其声明点,结束于其声明区域的末尾。”
#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

2
你的问题已经包含了答案:“一个被声明的名字……”。但是,这里并没有名字! - quamrana
在这个例子中:MyClass(12345) 是一种函数风格的转换,而不是声明。 - Richard Corden
仍然,该实例没有名称。 - artificialidiot
4个回答

16

您创建的对象

MyClass(12345);

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

MyClass m(12345);

块级作用域内存在的对象。


那个表达式不是主函数吗? - Kirill V. Lyadvinsky
1
这对我来说看起来是正确的。另一件事可能是优化:即使您使用第二种方法,编译器也可能将其优化为第一种方法。 - Anna
@Anna:没错。规格说明中提到了“潜在范围”,因此范围并没有被强制执行。编译器可以随意处理(包括提前销毁对象)。 - Philippe Leybaert
@JesperE,把它们传递给函数是正确的,因为函数调用将成为表达式。但是在这里我只看到创建临时对象。表达式在哪里? - Kirill V. Lyadvinsky
@Kirill:我没有看到问题。这种行为完全是预期的。规范谈论的是创建一个名称,而在你的代码片段中并非如此(你没有分配给变量)。 - Philippe Leybaert
显示剩余5条评论

8

你实际上是在创建一个对象,但没有将其保留在作用域内,因此它在创建后立即被销毁。这就是你所经历的行为。

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


5

回答你的其他问题。以下是逗号运算符的调用方式。它创建了一个MyClass临时对象,包括调用它的构造函数。然后评估第二个表达式cout << "Y" << endl,它将打印出“Y”。在完整表达式结束时,将销毁临时对象,这将调用其析构函数。所以你的期望是正确的。

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

为了使下面的内容正常工作,您应该添加括号,因为逗号在声明中具有预定义的含义。它将开始声明一个返回int且不带参数的函数some_func,并将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;

我看到过对作用域和生存期这两个术语的误用。

  • 作用域是指在不使用限定符的名称时可以引用名称的范围。名称有作用域,对象继承用于定义它们的名称的作用域(因此有时标准称其为“局部对象”)。临时对象没有作用域,因为它没有名称。同样,由new创建的对象也没有作用域。作用域是编译时属性。这个术语在标准中经常被误用,参见这个缺陷报告,所以很难找到真正的含义。

  • 生存期是一个运行时属性。它表示对象何时设置并准备好使用。对于类类型对象,生存期从构造函数结束执行时开始,直到析构函数开始执行时结束。生存期经常与作用域混淆,尽管这两个东西完全不同。

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


4

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

在块中声明的名称仅限于该块。其潜在范围从声明点开始,到其声明区域结束。

实际上,您没有声明任何名称。您的

MyClass (12345);

甚至不包含声明!它包含的是一个表达式,用于创建MyClass实例,计算表达式(然而,在这种特定情况下没有什么需要计算的),将其结果强制转换为void,并销毁在其中创建的对象。

更易理解的说法应该是

call_a_function(MyClass(12345));

你已经看到过很多次并且知道它是如何工作的,对吧?

规格说明还提到了“潜在”的范围,而不是“保证”的范围。因此,如果对象未在范围内使用,编译器可以更早地销毁该对象。 - Philippe Leybaert
5
“potential scope”有一个精确的含义:它是作用域加上因重新声明而隐藏名称的部分。即使对象不再使用,实现也不能更早地销毁它(这将破坏RAII的一些重要用途)。 - AProgrammer

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