views:

313

answers:

3

Suppose I have a class like

class Empty{
    Empty(int a){ cout << a; }
}

And then I invoke it using

int main(){
    Empty(2);
    return 0;
}

Will this cause any memory to be allocated on the stack for the creation of an "Empty" object? Obviously, the arguments need to be pushed onto the stack, but I don't want to incur any extra overhead. Basically I am using the constructor as a static member.

The reason I want to do this is because of templates. The actual code looks like

template <int which>
class FuncName{
    template <class T>
    FuncName(const T &value){
        if(which == 1){
            // specific behavior
        }else if(which == 2){
            // other specific behavior
        }
    }
};

which allows me to write something like

int main(){
    int a = 1;
    FuncName<1>(a);
}

so that I get to specialize one template parameter, while not having to specify the type of T. Also, I am hoping the compiler will optimize the other branches away inside the constructor. If anyone knows if this is true or how to check, that would be greatly appreciated. I assumed also that throwing templates into the situation does not change the "empty class" problem from above, is that right?

+2  A: 

It might, it might, not, depending on circumstances. If you say:

Empty e;
Empty * ep = & e;

then obviously things have to be allocated.

anon
Autopulated
@Autopulated: I believe so, see Richard Pennington's comment. The more interesting question is if you never ask for its address; then does it still have to have one? (A very zen-like question)
Victor Liu
If you take the address of e, and then do something with that address (which my example code doesn't) then e must be instantiated - i.e. allocated.
anon
For instance, if you compare pointers to two different objects of type Empty, then they have to be non-equal. So the compiler must then arrange to give them different addresses. Whether they're "actually on the stack" or not is beyond the scope of the language, but clearly in practice the compiler has to allocate addresses to them. Even if it were uncommitted space, do we really think that 1 byte of uncommitted address space is any cheaper to allocate than 1 byte of stack? If you never take the address, the compiler knows you'll never use it, and some compilers sometimes do elide the object.
Steve Jessop
+1  A: 

Try it and see. Many compilers will eliminate such temporary objects when asked to optimise their output.

If the disassembly is too complex, then create two functions with different numbers of such objects and see if there is any difference in the stack locations of objects surrounding them, something like:

void empty1 ( int x )
{
    using namespace std;

    int a;
    Empty e1 ( x );
    int b;

    cout << endl;
    cout << "empty1" << endl;
    cout << hex << int ( &x ) << " " << dec << ( &x - &a ) << endl;
    cout << hex << int ( &a ) << " " << dec << ( &a - &b ) << endl;
}

and then try running that compared with an empty8 function with eight Empties created. With g++ on x86, if you do take the address of any of the empties you get a location between x and a on the stack, hence including x in the output. You can't assume that the storage for objects will end up in the same order as they are declared in the source code.

Pete Kirkham
How? I'm looking at the g++ assembly listing for a simple example and it's very hard to follow. I'm using `g++ -c -g -O2 -Wa,-ahl=file.s file.cpp`
Victor Liu
You might get on better compiling to an executable (with debug info), then use `objdump -dS`, and search for the source lines you're interested in. Since you have optimisation on, remember that the same source line is liable to appear in multiple places in the disassembly.
Steve Jessop
+14  A: 

Quoting Stroustrup:

Why is the size of an empty class not zero? To ensure that the addresses of two different objects will be different. For the same reason, "new" always returns pointers to distinct objects. Consider:

class Empty { };

void f()
{
    Empty a, b;
    if (&a == &b) cout << "impossible: report error to compiler supplier";

    Empty* p1 = new Empty;
    Empty* p2 = new Empty;
    if (p1 == p2) cout << "impossible: report error to compiler supplier";
}   

There is an interesting rule that says that an empty base class need not be represented by a separate byte:

struct X : Empty {
    int a;
    // ...
};

void f(X* p)
{
    void* p1 = p;
    void* p2 = &p->a;
    if (p1 == p2) cout << "nice: good optimizer";
}

This optimization is safe and can be most useful. It allows a programmer to use empty classes to represent very simple concepts without overhead. Some current compilers provide this "empty base class optimization".

Richard Pennington
But what if the object created is never referenced at all? Is the compiler allowed to either reserve no stack space for it, or immediately clean it up even though it didn't go out of scope?
Victor Liu
Yes, I'm sure that a compiler will eliminate the reserved memory altogether if it isn't used or referenced. It's a pretty simple optimization. Depending on the compiler, of course.
Richard Pennington
As long as the behvior is not altered then the compiler is free to eliminate the whole concept of the object under the hood and the whole thing may be inlined into the code and all values stored in registers.
Martin York