Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

A Critical Overview of C++/CLI, Part I

Last updated Jan 1, 2003.

The importance of this issue can’t be overemphasized: according to Microsoft, C++/CLI is a C++ extension. The implications of this statement are obvious: if C++/CLI is allegedly a superset of C++ then sneaking .Net features into ISO C++ should be a "natural consequence". But how close are ISO C++ and C++/CLI at anyway? Which precarious features does C++/CLI introduce stealthily? What doesn’t Microsoft tell you about C++/CLI?

What’s in an "extension"?

In a short and rather non-committal statement on behalf of Microsoft, Herb Sutter, the ISO C++ committee’s secretary as well as the leading architect of Microsoft’s C++/CLI proprietary language, says:

Since 2003, Microsoft has been working with C++ community experts on the recently approved standard for C++/CLI (ECMA 372). C++/CLI is a set of conforming extensions to ISO standard C++ that enables C++ and ISO standard CLI (Common Language Infrastructure) to work well together. C++/CLI preserves full compatibility with C++ and adds support for CLI programming too, so that C++ programs can easily use the rich functionality of CLI (the leading commercial implementation of which is Microsoft’s .NET Framework). [emphases mine -- DK]

This statement implies that C++/CLI is a superset of C++, just as C++ is a superset of C. however, you will shortly see how imprecise this conclusion is. Without splitting hairs, "extensions to ISO C++" means exactly one thing to C++ programmers: their current investment in C++ will not perish if and when they switch to C++/CLI. More specifically, it means that every ISO compliant piece of C++ code can be ported as is to the new platform without breaking. Yet a quick glance at the recently approved standard for C++/CLI (ECMA 372) suggests otherwise. Before I discuss the silent omission of critical C++ features, I would like to present some of the highly-touted extensions.

Memory Management

The holy grail of the so called "new programming languages" is the presence of an automatic garbage collector (GC). C++/CLI, practically a clone of C#, also has this feature. However, it also supports the traditional dynamic memory management of C++. To enable both models to coexist, C++/CLI has a dual interface. The attribute "native" refers to standard C++ features with which you are familiar. By contrast, C++/CLI introduces a new dynamic memory allocation operator called gcnew which allocates objects that are subjected to garbage collection. The ^ operator is used for declaring a pointer to such an object:

Stack ^ps = gcnew Stack;

This dual interface leads to another inevitable borrowing from C# (ultimately, a Java feature): finalizers. A finalizer is a block of code that is executed when the GC reclaims the storage of an object allocated by gcnew. In the early 1990s, finalizers were considered the Java equivalent of destructors but programmers soon realized that they weren’t. Determinacy is the main difference between the two. A C++ destructor always executes at a predictable time -- when calling delete, when an automatic object goes out of scope etc. By contrast, a finalizer is executed at an unpredictable time after the object has become unreferenced. This could take seconds -- or weeks. When the GC is suspended or completely disabled, finalizers don’t run. This erratic behavior makes finalizers nearly useless. The easiest way to see it is by comparing a corpus of C++ code with a Java/C# corpus of the same size. The C++ destructor count compared to the number of finalizers is revealing. A ratio is about 1 finalizer for every 100 destructors shouldn’t come as a surprise. Why should all this matter? In C++/CLI, a class may have both a finalizer and a destructor!

ref struct R
{
~R(); //destructor, as you would expect
!R(); //finalizer
};

Why would you need a finalizer when a destructor is always a preferable choice? Although C++/CLI designers seem enamored with this duality, they haven’t really got a good answer. The confusing documentation claims that a finalizer serves as the "last chance" to release a resource, especially if the destructor failed to execute for some reason. I hate to say it but according to ISO C++, when a destructor fails to execute all bets are off anyway. The program’s behavior becomes undefined. Calling a finalizer a couple of hours after a satellite crashed because a destructor "failed to execute" isn’t really going to improve matters, is it?

Article 8.8.8 in the ECMA standard adds to the confusion by saying that a class that has a finalizer must also have a destructor. But if there is a destructor present, why on earth would you need a finalizer too? To confuse you further, it says that "the destructor should do the maximal cleanup possible. To facilitate this, the programmer should call the finalizer from the destructor...[emphases mine -- DK]" Good heavens! If the destructor takes care of cleaning up, why would it ever need to call a finalizer?? And what happens if that destructor "fails to execute" for some reason? Does that mean that only the finalizer will get called when the object is garbage collected? By contrast, if the destructor has run and called the finalizer, will the GC know not to call the finalizer once again? If you’re looking for answers, perhaps you should wait for next ECMA standard because the current one doesn’t have them.

Extensions? Contractions!

Thus far, we haven’t seen major deviations from ISO C++. Extensions and kludge? For sure. Yet in theory at least, you can stick to ISO C++ and hope for the best, right? Not quite. In many places, the vague and deliberately underspecified text of the ECMA standard implies that C++/CLI has jettisoned valid C++ features. These features are not supported in the new language, hence, C++/CLI is certainly not a "set of extensions" but perhaps the opposite. Two such randomly chosen features are local classes and bit-fields. The ECMA standard refers to both of them by way of negation: they are prohibited both in ref classes and value classes. ref classes are classes whose objects must be allocated using gcnew. value classes are more or less the equivalent of classes whose objects are passed by value in C++. native classes are standard C++ classes, with certain restrictions (I will discuss these in detail in part II). The ECMA standard doesn’t state whether local classes and bit-fields are restricted to native classes or they are completely unsupported.

Forget about Multiple Inheritance

It may appear as if I’m straining at a gnat. After all, how often are bit-fields and local classes used in C++? Not as rarely as one might assume. The "disposable class" idiom for instance neatly solves the finalization problem (more elegantly than would a finalizer, by the way). But C++/CLI has jettisoned one more important C++ feature. i.e., multiple inheritance (MI). Taking after C#/Java, C++/CLI supports only the single inheritance model. It also supports interface inheritance but real MI is strictly forbidden. What are the ramifications of this? Quite immense I dare say. The Standard Library uses real, hard core MI in every I/O-bound library, including <iostream>, <fstream> and <sstream>. Several Design Patterns also use MI. Does the lack of MI mean that <iostream>, <fstream> and <sstream> aren’t supported? Yes and no. Yes, they aren’t supported. No, the reason isn’t the absence of MI. Article 32 in the ECMA standard contains only one enigmatic sentence: "...the interaction between the CLI library and the Standard C and C++ Libraries is unspecified". This statement is deliberately vague. It tries to hide the hard facts: C++/CLI doesn’t support the Standard Library! And this, my friends, is the worst it can get. Are you ready to rewrite your STL-based code using the slower, so-called generic CLI library, thereby sacrificing both performance and portability? Is this what a "set of C++ extensions" really look like?

Article 32 is surprising for another reason. This non-committal statement is pretty Jesuitical, not to put too fine a point. If there were dozens of C++/CLI implementations running on multiple platforms, I would somehow accept it. Yet at present (and this isn’t going to change in the foreseeable future, consider the allegedly platform neutral C# language as a precedent), there is only one C++/CLI IDE running on exactly one platform: Visual Studio for Windows. So the status of the Standard Library is in fact pretty well specified -- it’s out, for good.

Next week I will dissect other C++/CLI features such as generics (not to be confused with templates), ref, value and native classes, delegates, static constructors and namespaces (not to be confused with...namespaces!).