Path: utzoo!attcan!uunet!inxsvcs!mwg From: mwg@inxsvcs.UUCP (Phil Blecker) Newsgroups: comp.lang.c++ Subject: Re: Two questions Summary: hopefully, some answers Message-ID: <143@inxsvcs.UUCP> Date: 29 Nov 88 01:35:15 GMT References: <13683@oberon.USC.EDU> Distribution: na Organization: Inx Services, Los Angeles Lines: 156 In article <13683@oberon.USC.EDU>, english@stromboli.usc.edu (Joe English) writes: > > In the Book, section 8.5.1 (Static Members), p. 275, it states: > "No initializer can be specified for a static member, and __it cannot > be of a class with a constructor__." That means that a static member of a class can be an instance of a class. But the static member's class cannot define a constructor. The class containing the static member CAN have a constructor. For instance: class has_constructor { int i; public: has_constructor( int j ) : i( j ) {} }; class no_constructor { int i; public: }; class static_member { static has_constructor h; // won't compile static no_constructor n; // will compile: n.i is uninitialized static int i; // will compile: i is uninitialized public: static_member(); }; Static members are initialized the same way any other static uninitialized data is during startup (usually [hopefully?] to all 0's). > > Second question: if automatic temporary objects are created when > necessary and are destroyed at the first opportunity (like when a > function returns a class object), how does the compiler know when the > object is no longer needed? > This is implementation dependent. When space is allocated automatically, it will not be overwritten during the scope of the object: that is guaranteed. Many compilers allocate space on the stack for ALL automatic objects declared when the function is entered, regardless of their scope (the function's stack space is not enlarged dynamically during the execution of a function). It is up to the compiler to create code that makes sure the object is only referred to within its proper scope. There may be other cases I can't think of right now, but automatic objects must at least be destroyed at the end of the block in which they are declared, or before a return statement causes the function to be exited (or before exiting if the function returns implicitly). If a class has a destructor, then this means that each instance's destructor will be called after calculating the value the return statement will return, but before the function is exited. It also means that the destructor is called before a block ends. So, a function which allocates many automatic objects and has many return statements will have many lines of code code to destroy the automatics (once before each return) if you're using a compiler that translates to C code (actual compilers might use another method that produces far less code). > > (What I want to happen is for the addresses of the (automatic) frobs > created by the calls to frob::frob(int) to be stored in v, and for the > frobs themselves to *remain intact* for the life of the function.) > There's no problem with this, although the method you used only works by accident in my opinion: > > v.append(frob(1)); // frob(1) returns a temporary > Basically what you have done is created a frob instance within the scope of a block that is a function call. Technicaly, the object should only exist for as long as it takes to call v.append(), and then be destroyed. Since there is no destructor associated with a frob, destroying the object does nothing; and since most compilers allocate space for the object when the function is entered, and deallocate the space when the function is exited, you are left with a side-effect that the object still exists even though it should have been destroyed. You can dramatically see the effects of this unless you put your code inside a loop. The compiler I have interprets the code you supplied as indicating that you have allocated actual stack space for only 2 frob objects, which are constantly reinitialized (by calling the frob constructor) because of the fact that a function call can be considered as block. In this case your vector would end up getting many, many pointers to the same 2 frob objects which are hanging around much longer than they should due to the implementation of the compiler. Thus the value of the objects pointed to in your array will vary depending on the current number of iterations through the loop; probably not what you expected. It would probably be better to use new to allocate the frobs, and design an interface that expects ~frobpvector to delete the frobs stored in the vector. Either that or at least include a warning to users of frobpvector. Just to make all these words easier to understand, I wrote an example using the code you originally supplied: class frob { int i; public: friend ostream &operator<<( ostream &o, frob &f ) { return o << f.i; } frob(int j) : i(j) {} ~frob() { cout << "~" << i << " "; } }; const int MAX=8; class frobpvector { // a vector of pointers to frobs frob **next; public: frob *list[MAX]; frobpvector() { next = list; } void append(frob&f) { *next++ = &f; } }; int main() { frobpvector v; for ( int i = 0; i < 6; ) { v.append(frob(i++)); v.append(frob(i++)); frob **next = v.list; for ( int j = i; j--; ) { cout << *(*next++) << " "; } cout << "\n"; } return 0; } and got the following (which is what I expected to happen): ~1 ~0 0 1 ~3 ~2 2 3 2 3 ~5 ~4 4 5 4 5 4 5 Note that the destructor is called twice each iteration of the loop (right after v.append()), and that the same memory space is used over and over again. I put the destructor in to graphically show that the space is only guaranteed to exist during the scope of the v.append() function. But the compiler, rather than dynamically allocating space on the stack, allocates space only once (which makes a lot of sense to me -- dynamically allocating stack space would be an extremely inefficient use of a CPU's time in this or any other case -- that's what new and delete are for -- to give the programmer control over dynamically allocated memory). If frob contained pointers to something, this could cause incredible havoc (especially is the pointers were to memory from new). If you really want to do things the way you showed, allocate the frob objects before the call to v.append(): frob f1( 1 ); v.append( f1 ); That will extend the scope to the block in which f1 is declared, which must be the entire block in which v is to be used. Hope that clears things up a bit -- it wasn't easy to describe briefly. If you have any further questions, feel free to contact me via e-mail or by phone (+1 818-243-3053). -- Phil Blecker none of my ideas belong to me and uunet!inxsvcs!mwg i can't see anything wrong with that