Your mutual dependency and memory puzzles resolved.
Copyright © 2002 Robert H. Schmidt
We finally care about the C++ Standard.
In this instance, we equals Microsoft. And while I usually dont take a Microsoft POV in my CUJ columns, this time I almost cant help myself.
I literally never, ever, thought wed care about C++ Standard conformance, beyond whatever it took to promote Windows development. I certainly never thought Id live to see the day that we purposely pushed our compiler technology to the top of the conformance stack. But by God, come early next year, the world will see compiler conformance not only beyond anything a sane predictor would expect of Redmond, but rivaling that of anyone else in the market.
I mean, we can even compile Loki. On purpose. Who saw that coming?
By the time you read this, the Visual C++ team will have published background documentation and white papers explaining the new features of this new Visual C++ release [1]. Among those papers should be a dissection of the newly supported conformance features. If you also care about conformance, and if you have the same reactions that my colleagues have had to this new compiler, I think youll be pleased and a little shocked.
The compiler will not ship by the time this column hits the streets, although it should be close, within a few months. Theres a chance how good or bad, I dont know that you can get a beta version. I personally prefer that we make the compiler easily and cheaply available, almost a giveaway. Im probably being overly idealistic. Im sure the financial and business practicalities are more complex than Ill ever know, especially as Visual C++ is just one organ of the Visual Studio .NET and .NET Framework behemoth.
If you already use Visual C++ .NET, this will be a worthy upgrade. If you use Visual C++ 6, or otherwise dont think you care about managed programming in the .NET world, this is still a worthy upgrade, if only for the conformance features. If you program Standard C++ on a non-Microsoft platform, this compiler can help you port your code to Microsoft platforms.
If nothing else motivates you, then allow some pity: if you all upgrade, I wont have to answer questions about Visual C++ 6 non-conformance anymore!
Q. Hi,
I really enjoy reading your column every month. Helps me quite a lot in my understanding of C++. And I have a question. Here, I simplify the situation as much as possible:
class B;
class A
{
public:
operator B()
{ // <=== error here
return B();
}
};
class B
{
public:
operator A()
{
return A();
}
};
The simple fix is to move the body of A::operator B() somewhere else, say into a .cpp file. Now, if I do that, it wont be inlinable for users of A. For performance issues, Id like to keep it as much as possible in the .h, so the compiler will still be able to inline the function.
Is there a workaround for this, or have I no choice but to also use a .cpp?
Best,
Steven Pigeon
A. Actually, theres an even simpler fix that gets you exactly what you want.
The problem, as your compiler has noted, is that B is an incomplete type at the point you try defining operator B. The problem is not the operator per se, for youd have the same trouble if you tried defining:
B f()or
void f(B)
at the same point: in all cases, B would be an incomplete type where the grammar required a complete type.
As you hint, you can declare not define, but declare operator B with impunity:
class A
{
public:
operator B(); // OK
};
You fear this tactic will force operator Bs definition into a separate source file:
//
// test.h
//
class B;
class A
{
public:
operator B();
};
class B
{
public:
operator A()
{
return A();
}
};
//
// test.cpp
//
#include test.h
A::operator B()
{
return B();
}
But think about this: once the preprocessor includes test.h in test.cpp, the resulting translation unit textually nets out to:
class B;
class A
{
public:
operator B();
};
class B
{
public:
operator A()
{
return A();
}
};
A::operator B()
{
return B();
}
as if youd written test.cpp that way all along, in one source file.
So what prevents you from writing test.h that way?
Nothing.
Your solution then: define A::operator B outside class As definition but inside the header file. You should also declare the operator as an inline function:
inline operator B();
to get the full effect you want.
The C and C++ ability to separate declarations from definitions can be confusing, especially to novice programmers, but it can also solve problems that are difficult or impossible to solve otherwise, as is the case here.
Past Life Regression
Q. Dear Bobby,
In C programming, I have been running into memory limitations. For example, the following simple program compiles fine:
char myArray[62632];
int main(void)
{
return 0;
}
But if I change 62632 to 62633, on my computer I get a compile-time error. Interestingly, if I move the declaration inside main, the error disappears.
Any ideas on what is taking place?
Thank you.
Yisrael Harris
A. Its just these sorts of questions that put the C back into CUJ!
I dont know whats going on but can make a likely guess, as your symptoms sound disturbingly like those I used to see in my bad old days of programming for Windows 2 with Microsofts C compiler. While I havent touched that combination in nigh on a decade, and may get a few specifics wrong below, I remember enough to illuminate your problem.
In old Windows programs using what Microsoft called the small and medium memory models, the compiler and linker stuffed certain kinds of data into a single shared near data segment. The segment was limited to what a 16-bit segment register could address, or 64 KB. If the combined size of all near data exceeded the 64-KB limit, the offending program would not build.
One could offset this problem by purposely declaring some data as far, or by using the compact or large memory models. These solutions had the side effect of putting data into additional segments that were more expensive to access. One could also dispense with statically declared objects and instead pull from the Windows-managed heap, through the once-infamous GlobalAlloc and related API calls.
While I dont know what compiler youre using, or what platform youre targeting, Im guessing that you are running into a similar limit. 64 KB equals 65,536 bytes, which is about 3,000 bytes larger than the size of your array. Your compiler may well bundle your array (62,633 bytes) plus other data and private compiler overhead (~3,000 bytes) into a combined area limited to 64 KB. By making your array too big, you are pushing the combined data size over this limit.
One way to test my theory is to add an extra byte to the static data size:
char myArray[62632];
char test_padding[1];
int main(void)
{
return 0;
}
then see if the program still compiles. Depending on how your compiler actually carves up memory, you may have to play with the size of test_padding to trigger the error. In general Id expect that as you increase the size of test_padding, you must decrease the size of myArray to avoid the error.
You say that moving the declaration inside main makes the error go away. I assume you mean this:
int main(void)
{
char myArray[62633];
return 0;
}
Now myArray has automatic storage duration instead of static storage duration. Compilers usually store such objects on the same stack used for function call arguments and return addresses. This stack is typically separate from the shared data area holding static objects. If thats the case for your compiler, then the array is no longer competing with other static data within a 64-KB arena.
The stack has a size limit too. That the compiler doesnt complain about myArray being too big doesnt mean that the stack has enough space for myArray. Unlike the static-data space which has a size fixed at compile time the stacks size fluctuates while a program runs. In my experience, out-of-stack-space conditions show up only at run time, not at compile time.
You can test this on your system by having your program effectively call main recursively:
void f(void)
{
char myArray[62633];
f();
}
int main(void)
{
f();
return 0;
}
At some point the stack will fill with nested copies of myArray. If the program terminates gracefully and especially if you built the program with stack probes or other debugging aids activated youll get a run-time message explaining that the stack overflowed. If the program terminates gracelessly, you may get random behavior or freezes.
Finally, if you declare this local myArray as static:
int main(void)
{
static char myArray[62633];
return 0;
}
you might get your original error message back. Since the array again has static storage duration, it is likely competing for part of the same (presumed 64 KB) space as before and should therefore cause the same problem as before.
By setting the right #pragmas, build options, or non-standard keywords, you might be able to increase the collective data limit or designate which static objects are pooled together. (Im completely speculating here, as I know nothing about your system.) I suggest you check out your compilers documentation or consult someone else who knows the technology.
The language standards do not directly address how compilers may pool data into common areas, or how big those common areas may be. At best the standards address the issue obliquely:
Q. Hi Bobby,
I wonder if you could shed some light as to why vector::erase is not required to call the destructor of the item being erased? I have recently run into a resource leak caused by this problem. I should mention that I am speaking to a vector<CWidget> and not to a vector<CWidget *>.
The problem I had was a widget with a member variable that was a string class. The string class dynamically allocates memory as needed, and the memory is cleaned up in the destructor. If my Widget destructor had been called on vector::erase, the resource would not have leaked!
This bug was discovered after untold hours of trying to fix a production system! Our current strategy is to avoid vectors whenever an erase may be called or to use vectors of pointers and take control of the destruction ourselves.
I have enclosed some sample code that demonstrates the problem. By the way, I am using Visual C++ 6 with the latest service packs.
Thanks for your time.
Ron Jacobs (avid CUJ reader)
A. My version of your test code appears as Listing 1.
Youve run into a distressingly common misconception of how vectors work. If its any consolation, the problem has nothing to do with Visual C++.
What you are seeing is correct vector behavior, or at least allowable vector behavior. When you erase a single element at index I from a vector of length N, the C++ Standard guarantees that:
The Standard does not say which elements will have the destructor and assignment operator called upon them. You are assuming that when you erase the element at a given index I (where I is 0 in your example), that precise element will have its destructor called. The Standard makes no such guarantee; indeed, based on how vectors are implemented, the Standard couldnt make this guarantee.
Remember, a vector is contiguous from its first element. When you erase an element, any trailing elements are shifted toward the front to fill the hole. The final element slot in the vector is thus rendered logically empty and can be destroyed.
The program in Listing 1 erases the first element of a three-element vector. When I run that program on several compilers, I see this event sequence:
Compare that sequence to what you want:
When it comes time for the vector itself to be destroyed, you are left with three elements: the two good ones and the third unused one. That makes for four total element-destructor calls: one during the erasure, plus three during the vector destruction. If you followed this pattern to its conclusion by continually erasing the front element, youd end up with six destructor calls to purge a three-element vector.
To get the behavior you want, you should try a list. As an experiment, substitute list for vector in my test program and then compare the results.
[1] <http://msdn.microsoft.com/visualc/techinfo/articles/>
[return to text]
Although Bobby Schmidt makes most of his living as a writer and content strategist for the Microsoft Developer Network (MSDN), he runs only Apple Macintoshes at home. In previous career incarnations, Bobby has been a pool hall operator, radio DJ, private investigator, and astronomer. You may summon him on the Internet via BobbySchmidt@mac.com.