2009-09-07 118 views
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);是表达式(和范围)。这是有道理的。因此,我希望下面的代码将打印“XY-X”总是:

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 2009-09-07 10:52:09

+0

在这个例子中:MyClass(12345)是一个函数式样转换,而不是一个声明。 – 2009-09-07 10:54:21

+0

仍然没有实例的名称 – artificialidiot 2009-09-07 11:04:29

回答

4

你正确地引用标准。我要强调:

一个宣布在一个块是局部的块。其潜在范围始于宣告地点,并在宣告地区结束时结束。

你没有宣布任何,其实。你线

MyClass (12345); 

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

一个不太令人困惑的事情会听起来像是

call_a_function(MyClass(12345)); 

你看到了很多次,知道它是如何工作的,不是吗?

+0

规格还讨论了“潜在”范围。不“保证”。因此,如果编译器未在范围内使用,它可以在早期销毁该对象。 – 2009-09-07 10:55:20

+4

“潜在作用域”具有确切含义:它是作用域加上由于重新声明而隐藏名称的部分。即使不再使用它,实现也不能破坏对象(这会破坏RAII btw的一些主要用途)。 – AProgrammer 2009-09-07 11:18:43

8

你实际上是在创建一个对象,而保持它的范围,所以它在创建后立即销毁。因此,你正在经历的行为。

您不能访问所创建的对象那么,为什么编译器保持它周围?

16

MyClass(12345); 

创建的对象是临时对象,其是仅在该表达活着;

MyClass m(12345); 

是一个对象,它是活着的整个块。

+0

是否*表达*是主要功能? – 2009-09-07 10:43:11

+1

这似乎对我来说。另一件事可能是优化:即使你使用第二种方法,编译器可能会优化它到第一种。 – Anna 2009-09-07 10:43:17

+0

@安娜,在第二种情况下,它总是会打印xYx。 – 2009-09-07 10:44:05

5

为了回答您的其他问题。以下是逗号运算符的调用。它创建一个MyClass临时表,其中包括调用其构造函数。然后评估第二个表达式cout << "Y" << 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是一个运行属性。这意味着对象何时被设置并准备好使用。对于类类型的对象,生命期在构造函数结束执行时开始,并在析构函数开始执行时结束。生活经常与范围混淆,尽管这两件事情完全不同。

    临时的生命周期是精确定义的。他们中的大多数在评估它们包含的完整表达式后(例如,上面的逗号操作符或赋值表达式),它们会终止生命周期。临时可以绑定到会延长其寿命的常量引用。抛出异常的对象也是临时对象,当它们不再有处理程序时,它们的生命周期结束。