Is there a valid case for creating a temporary that is immediately destroyed and is not used directly in C++?
Inspired by this question. Suppose I have class Lock
with a default constructor and in some code I write the following statement:
Lock();
this will have the effect of creating a temporary object of class Lock
and immediately destroying it. Of course, creation could have some side effects and that would alter the program behavior, however that looks rather weird.
So my first guess is that such statements while completely valid from the language perspective are highly likely to contain a logical error.
Are there some valid usecases of the above statement? Are there some well-know开发者_如何学编程n and popular idioms that include such statements? Why would I want such statements in a correct program?
It doesn't look any weirder than a call to a void function named Lock
.
Not that I'm suggesting that you go around thinking of functions as just funny-named constructors (of whatever their return type is), but the syntax is intentionally similar.
I can't for the moment think of a good reason to create but not use a Lock
, but:
LockSession(lock);
has the same side-effects as you'd expect from:
acquire_and_then_immediately_release(lock);
As you say, this is very rarely what you want, so it might well look to the casual reader like an error. If for some odd reason it is what you want, and you want to avoid confusing people, you can do:
{
LockSession session(lock);
// release immediately
}
or for that matter:
void acquire_and_then_immediately_release(Lock &lock) {
LockSession(lock);
}
with the same effect and less chance of head-scratching whether that's really what you meant, or whether you've just made the common mistake of forgetting to supply a name and hence holding the lock for less time than you should.
Why would you want to acquire and then immediately release a lock? Probably because you're (ab)using it as a slightly peculiar semaphore. For example you could create a bunch of threads with a lock held, and if the first thing each thread does is this, then none of them will get past it until all the thread creation is done (plus anything else the parent wants the threads to be able to see) and the parent releases the lock. There are probably better examples out there of objects whose construction side-effects are significant.
Moving away from side-effects, another possibility is that if you want to check that a string is valid XML, you could write:
xml::dom::Document(mystring);
expecting an exception for invalid data. Again, the code would be more readable if you called a well-named function that does it. And constructing a DOM tree is not the most efficient way to validate XML. But if a class is what you already have then using it is the simplest thing that works.
I suppose the issue is that the name of a class is rarely descriptive of the side-effects of creating and destroying it. So a lone temporary will tend to look weird for that reason.
In order to get away from dangerous va_list
style variadics, I've written code that you call like this:
MyFunc() << a << b << c;
Where MyFunc
is implemented as a class with an operator <<
and ~MyFunc()
is responsible for doing the desired work. It's awkward to write the MyFunc
class, but once it's done, it works well.
So, yes, the pattern of instantiating a temporary object that only lasts for one line can be useful.
Similar syntax of creating a temporary is used many a times in template
metaprograms or boost
kind of libraries. They actually don't create temporary, but simulate its effect.
e.g. is_base_of
You can see temporary object syntax in the sizeof()
trick.
One use case that I can think right now, is related to tempalte metaprogramming.
As you know, you cannot partially specialise a function, while you can do that with classes. To circumvent this problem, one might implement a function as a class, inside the constructor and partially specialise the whole class as he pleases.
I encountered such problem once recently. I needed a function that would recursively call itself, but I wanted that recurssion to be resolved at compile time. In my case, however, I used a static function inside such class, because often I want it to return something different than void
.
Consider the following example:
#include <stdio.h>
template <typename T, int i>
struct megapower {
megapower(T &val) {
val*=val;
{megapower<T,i-1> tmp(val);}
}
};
template <typename T>
struct megapower<T,1> {
megapower(T &val) {}
};
int main() {
int v=2;
{megapower<int,5> tmp(v);}
printf("%d\n",v);
return 0;
}
It will compute 2^2^2^2^2 = 65536.
P.S. As it turns out, you cannot write megapower<int,5>(v)
. The compiler will think that you are trying to create a new object v
of that type.
Compiler's well within rights to eliminate those variables and ellide constructors with side effects from memory.
精彩评论