views:

291

answers:

4

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
+8  A: 

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?

Philippe Leybaert
+15  A: 

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.

JesperE
Isn't *that expression* is the main function?
Kirill V. Lyadvinsky
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.
Anna
@Anna, in the second case it will always print xYx.
Kirill V. Lyadvinsky
@Kirill: that expression is in a statement in `main()`. It is not the whole of `main()`.
Dave Hinton
@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)
Philippe Leybaert
@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.
JesperE
@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?
Kirill V. Lyadvinsky
@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).
Philippe Leybaert
@Philippe, compiler cannot destroy the object early in the second case. In that case using `boost::scoped_lock` will be impossible.
Kirill V. Lyadvinsky
@Kirill: the string is "MyClass(12345)" is the expression.
JesperE
+2  A: 

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.

Less confusing thing would sound like

call_a_function(MyClass(12345));

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

Pavel Shved
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.
Philippe Leybaert
"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).
AProgrammer
+4  A: 

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.

Johannes Schaub - litb