Local variable scope question [duplicate]
Asked Answered
J

4

8

Why is the following code prints "xxY"? Shouldn't local variables live in the scope of whole function? Can I use such behavior or this will be changed in future C++ standard?

I thought that according to C++ Standard 3.3.2 "A name declared in a block is local to that block. Its potential scope begins at its point of declaration and ends at the end of its declarative region."

#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;
}

Based on the responses I can assume that MyClass(12345); is the expression (and scope). That is make sense. So I expect that the following code will print "xYx" always:

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

And it is allowed to make such replacement:

// 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
Johnathon answered 7/9, 2009 at 10:37 Comment(3)
Your question contains the answer: A name declared.... There is no name!Dual
In this example: MyClass(12345) is a function style cast, not a declaration.Hackler
Still, there is no name for the instanceBeghard
R
5

To answer your other questions. The following is the invocation of the comma operator. It creates a MyClass temporary, which includes calling its constructor. It then evaluates the second expression cout << "Y" << endl which will print out the Y. It then, at the end of the full expression, will destroy the temporary, which will call its destructor. So your expectations were right.

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

For the following to work, you should add parentheses, because the comma has a predefined meaning in declarations. It would start declaring a function some_func returning an int and taking no parameters and would assign the scoped_lock object to x. Using parentheses, you say that the whole thing is a single comma operator expression instead.

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

It should be noted that the following two lines are equivalent. The first does not create a temporary unnamed object using my_mutex as the constructor argument, but instead the parentheses around the name are redundant. Don't let the syntax confuse you.

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

I've seen misuse of the terms scope and lifetime.

  • Scope is where you can refer to a name without qualifying its name. Names have scopes, and objects inherit the scope of the name used to define them (thus sometimes the Standard says "local object"). A temporary object has no scope, because it's got no name. Likewise, an object created by new has no scope. Scope is a compile time property. This term is frequently misused in the Standard, see this defect report, so it's quite confusing to find a real meaning.

  • Lifetime is a runtime property. It means when the object is set up and ready for use. For a class type object, the lifetime begins when the constructor ends execution, and it ends when the destructor begins execution. Lifetime is often confused with scope, although these two things are completely different.

    The lifetime of temporaries is precisely defined. Most of them end lifetime after evaluation of the full expression they are contained in (like, the comma operator of above, or an assignment expression). Temporaries can be bound to const references which will lengthen their lifetime. Objects being thrown in exceptions are temporaries too, and their lifetime ends when there is no handler for them anymore.

Ripleigh answered 7/9, 2009 at 15:54 Comment(0)
T
16

The object created in your

MyClass(12345);

is a temporary object which is only alive in that expression;

MyClass m(12345);

is an object which is alive for the entire block.

Toast answered 7/9, 2009 at 10:40 Comment(10)
Isn't that expression is the main function?Johnathon
That seems right to me. Another thing could be optimization: even if you do use the second method, the compiler might optimize it to the first one.Illiquid
@Anna, in the second case it will always print xYx.Johnathon
@Kirill: that expression is in a statement in main(). It is not the whole of main().Brownie
@Anna: that's right. The specs talk about "potential scope", so the scope is not enforced. The compiler can do whatever it wants (including destroying the object early)Brittaney
@Kirill: note my use of the word "temporary". This way of creating objects is useful when creating object for the sole purpose of passing them to a function: foo(MyClass(12345)), in which case the object will exist for the duration of the function call and then destroyed.Toast
@JesperE, that's right for passing them to a function, because the function call will be expression. But here I can see only creating temporary object. Where expression is?Johnathon
@Kirill: I don't see the problem. The behavior is totally expected. The specs talk about a name being created, which is not the case in your piece of code (you're not assigning to a variable).Brittaney
@Philippe, compiler cannot destroy the object early in the second case. In that case using boost::scoped_lock will be impossible.Johnathon
@Kirill: the string is "MyClass(12345)" is the expression.Toast
B
8

You're actually creating an object without keeping it in scope, so it is destroyed right after it is created. Hence the behavior you're experiencing.

You can't access the created object so why would the compiler keep it around?

Brittaney answered 7/9, 2009 at 10:39 Comment(0)
R
5

To answer your other questions. The following is the invocation of the comma operator. It creates a MyClass temporary, which includes calling its constructor. It then evaluates the second expression cout << "Y" << endl which will print out the Y. It then, at the end of the full expression, will destroy the temporary, which will call its destructor. So your expectations were right.

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

For the following to work, you should add parentheses, because the comma has a predefined meaning in declarations. It would start declaring a function some_func returning an int and taking no parameters and would assign the scoped_lock object to x. Using parentheses, you say that the whole thing is a single comma operator expression instead.

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

It should be noted that the following two lines are equivalent. The first does not create a temporary unnamed object using my_mutex as the constructor argument, but instead the parentheses around the name are redundant. Don't let the syntax confuse you.

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

I've seen misuse of the terms scope and lifetime.

  • Scope is where you can refer to a name without qualifying its name. Names have scopes, and objects inherit the scope of the name used to define them (thus sometimes the Standard says "local object"). A temporary object has no scope, because it's got no name. Likewise, an object created by new has no scope. Scope is a compile time property. This term is frequently misused in the Standard, see this defect report, so it's quite confusing to find a real meaning.

  • Lifetime is a runtime property. It means when the object is set up and ready for use. For a class type object, the lifetime begins when the constructor ends execution, and it ends when the destructor begins execution. Lifetime is often confused with scope, although these two things are completely different.

    The lifetime of temporaries is precisely defined. Most of them end lifetime after evaluation of the full expression they are contained in (like, the comma operator of above, or an assignment expression). Temporaries can be bound to const references which will lengthen their lifetime. Objects being thrown in exceptions are temporaries too, and their lifetime ends when there is no handler for them anymore.

Ripleigh answered 7/9, 2009 at 15:54 Comment(0)
C
4

You quoted standard correctly. Let me emphasize:

A name declared in a block is local to that block. Its potential scope begins at its point of declaration and ends at the end of its declarative region.

You didn't declare any name, actually. Your line

MyClass (12345);

does not even contain a declaration! What it contains is an expression that creates an instance of MyClass, computes the expression (however, in this particular case there's nothing to compute), and casts its result to void, and destroys the objects created there.

A less confusing thing would sound like

call_a_function(MyClass(12345));

You saw it many times and know how it works, don't you?

Culbertson answered 7/9, 2009 at 10:50 Comment(2)
The specs also talk about "potential" scope. Not "guaranteed". So the compiler can destroy the object earlier if it is not used in the scope.Brittaney
"potential scope" has a precise meaning: it is the scope plus the parts where the name is hidden due to re-declaration. An implementation can't destroy an object earlier even if it is no more used (that would break some major use of RAII btw).Izabel

© 2022 - 2024 — McMap. All rights reserved.