Back to TOC Departments


We Have Mail


Letters to the editor may be sent via email to cujed@mfi.com, or via the postal service to Letters to the Editor, C/C++ Users Journal, 1601 W. 23rd St., Ste 200, Lawrence, KS 66046-2700.


Dear Editor,

While reading the article "A Quick and Simple Memory Allocator" in the January 1998 issue of CUJ, I was a little disappointed by the approach taken by the author, Vladimir Batov. In the effort to show how powerful yet easy to write custom memory allocators can be, he omits a number of very important details. I found the discussion in the Limitations and Extensions section of the article highly superficial. Important limitations that could lead to subtle bugs were omitted. In this letter I'll try to address some of these issues.

Most Standard Template Library (STL) implementations do not use static allocators. The reason is that the invocation order of static objects' constructors is not guaranteed. This means that the simple memory allocators presented in the article has a subtle time bomb ticking inside its implementation. When class Foo, utilizing the proposed memory allocation mechanism, is used as a non-local static object or inside non-local static objects, and when the call to Foo::operator new occurs before the constructor of Foo::_allocator has been invoked, the results will be indeterminate. Worse yet, this behavior is likely to change with the link order and the brand of compiler used. Now you see it, now you don't. This is really bad.

There are many good articles that go into the details of the problem. I believe it was Jerry Schwartz who first presented a comprehensive solution in his article "Initializing Static Variables in C++ Libraries" published in the C++ Report. You can use static allocators, you just need to initialize them properly with a singleton pattern. Same performance as Batov's solution, none of the danger.

Scott Meyers presents a clean, no-nonsense solution in Item 47 of Effective C++. Meyer's solution relies on the fact that local static objects are initialized upon the first entry into the block they are defined in. Schwartz's and Meyer's approaches can be combined to create a singleton object that is well suited for multi-threaded applications.

Below I've included my implementation of a thread-safe singleton template class. As you can see, the whole implementation is inlined and should theoretically cause no overhead as far as Batov's memory allocators are concerned.

template<class T>
class Singleton
{
     struct Initializer
     {
          Initializer(void)
              { Singleton<T>::get(); }
     };
     static Initializer _initializer;
     Singleton(void) {}
     Singleton(const Singleton&);
     operator=(const Singleton&);
public:
     static T& get(void)
     {
          static T t;
          return t;
     }
};

Back to the simple memory allocator. I find it surprising that the author does not mention what can be the biggest limitation resulting from the overloading of the class-level new and delete operators. If you derive class Bar from Foo such that sizeof(Bar) > sizeof(Foo), you must provide an implementation of Bar::operator new and Bar::operator delete. Failing to do so is a recipe for disaster. Although providing an implementation is relatively trivial, it is very easy to forget, especially if someone other than the original author of Foo does the subclassing. The situation is documented in Section 13.10.3 "Allocators and Deallocators" in the second edition of Stroustrup's The C++ Programming Language. Unfortunately, there is no fix for the problem short of providing an allocator for objects of various sizes.

Writing custom memory allocators may seem easy, and in fact it is, as long as objects utilizing them are used in a very restricted manner. However, writing robust memory allocators that can be efficiently used in a variety of environments requires a lot more careful thinking.

I do hope that you will find a way to alert CUJ readers of the many unmentioned limitations of the memory allocator presented in the January 1998 issue. I think this is essential in preserving the image of CUJ as a publication that tries hard to present robust, bug-free approaches to frequently encountered problems in software development.

Keep up the good work.

Regards,

Simeon Simeonov
Manager of Language Technology
Allaire Corporation
Cambridge, MA

Thank you for your letter. With apologies to our readers, the static initialization bug lurking in Vladimir's MemoryAllocator plainly slipped by us. (Apparently, this Quick and Simple Allocator is a little too simple.) I've edited out your other comments, mainly because they point out what are limitations, not hidden gotchas, in the implementation. These are best discussed in a venue where we have more room, namely the web.

Readers will find a full discussion of the issues cited by Mr. Simeonov and other readers at http://www.cuj.com/forum/. I think the discussion is instructive, especially for programmers using or implementing their own special-purpose memory allocators. A couple of the other points covered: how to make MemoryAllocator thread- and exception-safe, and limitations and improvements to the allocator's performance for large n. — mb


Dear P.J.

Like yourself I am a bit fond of the command line. Here are my two main Windows survival tips:

1. To go up one directory in Windows Explorer, press BACKSPACE.

2. If you drop a file onto a cmd window, it types the fully qualified filename at the command prompt. This makes working with files on the desktop fairly easy.

Love the magazine, keep up the good work,

John O'Halloran


Dear Mr. Plauger:

I am a C and C++ programmer who is familiar with several object-oriented languages. I am somewhat familiar with the ANSI/ISO Draft C++ Standard and I have heard that you are part of the C++ standards committee. I feel that there are a few deficiencies in the draft standard. I have a few important and serious suggestions which, if implemented, would make C++ a somewhat more robust language. I am sure you will be tempted to dismiss my suggestions right away, but please hear me out and give them some serious thought. I know several other programmers who agree with me.

First of all, let's say that class B derives (publicly) from class A, and that class A has an assignment operator (operator=) defined, while class B does not. I have read that if you have the following code:

B a, b;
...
a = b;

then class A's assignment operator is not used. Instead, a member-by-member copy is done from b to a. This is not how it should work. Instead, class A's assignment operator should be called, and only the data members not belonging to class A should be copied member-by-member. This is what many compilers have done in the past, and should be how it is done in the Standard.

Second, the STL string class should have an operator const char*() which does exactly the same thing as the c_str() member function. With this operator, along with the string class's constructor that takes a char* as an argument, the programmer could pass strings to functions without having to worry about whether they call for a C string or a C++ string, and a C++ string can be used in place of a string literal. Many other C++ libraries, including a previous Rogue Wave library used with Unix compilers, have had string classes with just such an operator, and that operator should be added to the Standard. It would not violate any object-oriented programming ideologies any more than the c_str() member function does (which it does not).

Finally, an operator== and an operator!= should be automatically generated for any class that does not have them defined. For a class that is not derived from another class, they would do a member-by-member comparison. For a class that is derived from another class, they would call the respective operators (defined or compiler-generated) from the base class and do a member-by-member comparison of the elements of the derived class that are not members of the base class. I know that this would not be trivial to implement (and is therefore not implemented in C), but neither is exception handling. Also, most other object-oriented languages provide comparison operators for classes. Thus, adding this to the standard should be seriously considered.

Thank you for your consideration. I hope you will take these suggestions seriously. I am considering subscribing to the C/C++ Users Journal.

Lyle Goldman

You're a little late, unfortunately. The C++ Standard is now frozen. In any event, I'll remark on your suggestions. My understanding is that the base object is assigned using its own assignment operator if it has one, just as you desire. We intentionally omitted an implicit cast to pointer from class string because earlier experience revealed a whole host of ambiguities and undesirable implicit conversions when the cast was present. Finally, I never heard a debate about adding generated equality comparisons as you describe, but they're certainly little more ambitious than generated assignments. They just didn't get considered, to my knowledge. — pjp


Dear Mr. Plauger:

In the January 1998 issue's "We Have Mail" section, E. Fletcher Dunn was responding to last October's article on "Compile-Time Assertions in C++," by Kevin S. Van Horn. Dunn noted a different technique for compile-time assertions that used a typedef rather than a template. The last sentence of your response to this letter mentioned that the template solution was able to do "tricks" that the typedef solution cannot. I'm very curious as to what some of those may be, considering that both solutions seem to do pretty much the same thing (i.e., generate an invalid array size constant).

Thanks,

Scott Bilas
iCat Corporation
scottb@icat.com

Template instantiation can involve recursion and conditional specialization. Hence, you can compute all sorts of functions not available with preprocessor arithmetic or constant integer expressions. Browse through an implementation of STL sometime, or chase links on generic programming on the web. You'll be astonished at what you find. — pjp


Dear Editor and John L. Gammel,

I read John's letter in the December 1997 issue regarding a C++ compiler that can address 128 MB physical RAM. I use a 32-bit C/C++ compiler called DJGPP (see http://www.delorie.com/djgpp/ and ftp://ftp.simtel.net/pub/simtelnet/gnu/djgpp/). With DJGPP you can access up to 4 GB worth of RAM — all the physical RAM available on the target system + 256 MB of virtual RAM. DJGPP is based on GNU CC and g++, and produces only 32-bit DOS protected mode applications.

However you cannot produce a Windows app nor a Win32 console application with stock DJGPP. There is something called "RSX/NT for DJGPP" (see ftp://ftp.simtel.net/pub/simtelnet/gnu/djgpp/v2tk/rsxntdj1.zip) that turns DJGPP into a Win32 console application compiler. I haven't tried "RSX/NT for DJGPP" so I cannot comment on this.

Hope this will help.

Sincerely yours,

Trond Endrestol
endrestol@hotmail.com


Dear CUJ:

In his February 1998 article, "Porting a C++ Application to Java," Danny Kalev discussed two alternative techniques for implementing the Java equivalent of C++ enums. One involved defining a single interface with static int constants for the enumerated values. The other defined each enumerated value as a separate subclass, each deriving from a common base class. The first approach has the drawback that by using ints, there's no explicit type checking for the enum type. The second method, while providing type safety, creates the burden of having to generate multiple subclasses, each in its own file.

Here's a third variation which provides type safety, a fixed set of values, and requires only one class file. Declare the enum type as a class with a private constructor, an equals method, and each enumerated value as a public static constant instance of the class. For example,

class TransactionType
{
    public static final TransactionType
        SELECT=new TransactionType(1);
    public static final TransactionType
        INSERT=new TransactionType(2);
    public static final TransactionType
        UPDATE=new TransactionType(3);
    // ... etc.
    private int value;
    public boolean equals(
        TransactionType type)
    {
        return type.value == 
            this.value;
    }
    private TransactionType(int val)
    {
        this.value=val;
    }
}

If having to specify the enum values with the class qualifier (as in TransactionType.SELECT) is felt to be too burdensome, one can do the following variant. Instead of making the constructor private, give it package-level access, and move the static constants to a separate interface class which can then be implemented by classes which need to reference the values (as in Kalev's first approach). The interface and enum class are put into a separate package to keep the set of allowed values fixed.

// TransactionType.java
package constants;
class TransactionType
{
    private int value;
    public boolean equals
     (TransactionType type)
    {
        return type.value == 
            this.value;
    }
    // Package-level access
    TransactionType(int val)
    {
        this.value=val;
    }
}
     
// TransactionTypeValues.java
package constants;
import constants.TransactionType;
interface TransactionTypeValues
{
    static final TransactionType
        SELECT=new TransactionType(1);
    static final TransactionType
        INSERT=new TransactionType(2);
    static final TransactionType
        UPDATE=new TransactionType(3);
    // ... etc.
}

Jerry Liebelson
JLWare Inc.
jl@jlware.com


Dr. Plauger,

I have a question for you as the author of The Draft Standard C++ Library and the Visual C++ 5.0 standard library. When you read the last item in a stream called say, instr, via instr >> x, eof() becomes true even though you still have the item x to process. From your book, this seems to be the correct standard behavior, though it seems unintuitive to me and seems to contradict what cin does with terminal input. In addition, based on the standard, it would seem that if x were followed by blanks (or even a newline) in instr, eof() would not become true. In the Visual C++ library, though, I think it does become true (which, given the behavior without extra whitespace), would seem correct.

Anyway, my questions are:

1. Is my interpretation of what happens correct and according to the Standard?

2. How do you go about setting up a loop which processes till end of file in these circumstances?

The answer to question 2 may well involve something like the code on p. 173 of your book. One might write

if (!(instr >> x))
    break;

I hate constructs like this. I think it is one of the most frequent sources of errors and mystery in C/C++ programming. But can you say:

if ((instr >> x) == 0)
    .....
Or even better:
instr>>x;
if (instr == 0)
    .....

Which leads to another question. I have seen the if (instr >> x) construct in a few other places as well, but I have never seen any kind of explanation of why it works. After all, instr >> x is just instr, and isn't subject to being zero or non-zero. In the ARM, it says that an if condition has to evaluate to a number, a pointer, or a class with a conversion to one of those. Does istream have such a conversion?

Sorry to bother you with such petty matters, but I would appreciate any help you can give me.

William B. Jones
http://csc.csudh.edu/wbj/jones.html

An extractor stops gobbling characters when it encounters end of file or a character that doesn't match the pattern for the input field. Trailing spaces thus prevent an extractor from setting the stored end-of-file indicator in the stream. All of the above idioms for testing end of input work because class istream defines a cast to a void pointer, which can be compared (explicitly or implicitly) against zero to make a boolean test. The value is a null pointer if a subsequent extractor call will assuredly fail. — pjp


Dear CUJ,

I will not be renewing my subscription after the February 1998 issue. The reason has nothing to do with editorial policy. I have been writing C code since the mid 1980s in hobby and academic settings. I taught myself C while others were working on BASIC and TurboPascal. I never cared to make the transition to C++. However, I am learning Java using the JDK for Linux. The reason for my leaving C is summed up very well in the advertisement on page 82 for PC-lint. I have spent too much of my life chasing array index bugs.

Within the first few hours of starting up the JDK, I made my first array index error. The compiler found it. I fixed it. End of problem. End of C. As Jack Purdum said in his EcoSoft C ads, your total performance depends heavily on how fast you can develop code that runs, not just how fast that code runs.

Thanks to the many people at the magazine and also to my fellow subscribers who have made it a great magazine. I started just before the merger of C Users Group and C Journal. I learned a lot about C, and it has been a great ride.

Best Wishes,

Ronald Michaels
michael@planetc.com

I would guess that someone who keeps making array index errors is using lots of arrays in non-trivial algorithms. The irony is, it's those sorts of applications that most often require the performance of C/C++. But maybe your situation is different. Anyway, have fun with Java, and remember, our door's always open if things don't work out. — mb


Gentlemen:

I'm not sure which of you is the most appropriate one to ask this question, so I'll ask you all.

I'm a new subscriber to CUJ and also new to C++. Therein lies the problem: much of what I see in CUJ would seem to work well for the person experienced in C++, but not necessarily for the novice (like myself).

So the question is: what resources (books, classes, and even which compiler) would you recommend for the novice who cannot afford to stay a novice ?? I'm getting involved with C++ at work after many years of languages like assembler, FORTRAN, and some C. Please do not "wimp out" with me and tell me that there are many fine books, etc. Please, please make specific recommendations. I am particularly interested in purchasing a C++ compiler to host on my system at home so that I can study in my spare time. I would be particularly interested in references that would also provide some treatment of the object-oriented capabilities of C++.

What effect does the release of the new C++ Standard have on your recommendations ?

Thanks.

Tom Zawodny
zawodny@erinet.com

Books: Thinking in C++, by Bruce Eckel. Prentice-Hall, 1995. ISBN 0-13-9177094; C++ Primer, Second Edition, by Stanley Lipmann. Addison-Wesley, 1991. ISBN 0-201-16487-6; C++ Strategies and Tactics, by Robert Murray. Addison-Wesley, 1993. ISBN 0-201-56382-7. The C++ Programming Language, Third Edition, by Bjarne Stroustrup. Addison-Wesley, 1997. ISBN 0-201-88954-4.

Newsgroups are a great resource, because you can ask questions and quickly receive competent answers. Check out comp.lang.c++ and comp.lang.c++.moderated.

I'll semi-wimp out and not recommend a specific commercially available compiler. But you can get one free for DOS. See Trond Endrestol's letter over on page 107.

The "new C++ Standard" isn't quite a standard yet. What has actually been released is the Final Draft International Standard (FDIS). For your purposes, the primary significance of the FDIS is that C++ won't go changing on you as you learn the language. The FDIS release's effect on my recommendations is minimal: when you start using really new features, like namespaces, get some newer books; and of course, keep reading CUJ. —mb.