Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

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

Last updated Jan 1, 2003.

C++/CLI introduces many features that are new to a C++ programmer. However, very of few of them if any, are original. Most of them have been available for years in other programming languages e.g., C#, Java or even Borland’s VCL. Here is my take on the usefulness and engineering merits of some of these features.

Classes of Classes

In an ideal programming language, a class would look exactly as they do in C++. Should an object o1 of class A be allocated on the static memory, the stack or the free store? This is an implementation decision that is made when you instantiate the object. Similarly, whether objects are passed by value, as pointer or by reference is an implementation decision that should be made on a per-object basis. these properties aren’t and shouldn’t be enforced on a per class basis, except for highly-specialized cases. C++/CLI however is different. It forces you to determine at design time, i.e., in the class declaration, where the objects will be stored and how they will be passed. Design-wise, this policy is a step or two backwards, no matter what the C++/CLI rationale tells you. But why is C++/CLI different? Because it’s very difficult to design a programming language that allows objects of the same class to have different runtime properties, e.g., object a1 is garbage collected whereas object a2 is allocated on the stack. Consequently, C++/CLI doesn’t allow to write something like this:

string *pstr = new string;
string str;
string & sr=str;

Java designers solved this problem in a brute-force, though consistent manner: all Java objects are GC-bound, whereas primitive types are allocated on the stack. C++/CLI tries (not very successfully) to combine the two models by permitting different storage types for objects. However, you have to decide at design time how the objects of that class will be represented.

ref Classes

A ref class is a class whose objects must be allocated using gcnew. They are garbage collected so you can’t create them on the stack or the native heap; needless to say, you should never delete them:

public ref class Stack
{
//..
};
Stack ^ps = gcnew Stack;//OK
ps->Push(Element);
Stack s; //error
Stack *p = new Stack; //error

The access specifier before the class declaration is called "top-level type visibility". It can be either public, meaning the class is visible from other assemblies, or it can be private, which means that the class is visible only inside its assembly. An assembly is roughly equivalent to an .obj file produced from a C++ translation unit.

value Classes

A value class is used for small objects that should have value semantics. Candidates for value classes are a complex number class, smart pointers and handles:

public value class Point
{
 int x;
 int y;
//..
};
Point p1;

interface Classes

An interface class is equivalent to an abstract class in C++, with some syntactic sugar and the inevitable verbiage of the "new programming languages":

interface class IControl
{
 virtual void Pain();
};
public ref class : IControl, IDataBound //multiple interface inheritance
{
//..implement the interfaces
};

The ref and value specifiers are necessary for .Net’s internal bookkeeping, not because they really add anything to the powerfulness of this language. When a class is declared as ref or value, the .Net machinery generates the necessary runtime metadata and additional code scaffolding for it.

Delegates

A little bit of history: the litigation between Sun and Microsoft in the late 1990s can be ascribed to a single keyword: delegate. Sun’s original Java specification didn’t have this keyword. Microsoft added it to its Java implementation, thereby breaking source code compatibility with Sun’s Java. This litigation ended in a settlement but the impact was tremendous: In 2000, Microsoft came up with its Java clone, a language called C#. Now Microsoft was free to do as it saw fit, including the reintroduction of delegates. It’s no surprise that C++/CLI has delegates, too. A delegate is an object that encapsulates a callable entity. It’s roughly equivalent to TR1 std::tr1::function class template, which allows you to treat freestanding functions, static member functions, nonstatic member functions etc., uniformly as long as they have the same signature. Yet unlike std::tr1::function, a C++/CLI delegate can have an invocation list of multiple functions, not just one. Oddly enough, delegates are GC-bound objects. Therefore, you must create them using gcnew:

public ref class A
{
public:
static void F(int);
 void G(int);
};
delegate void MyF(int); //define a delegate type
A ^a= gcnew A;
//create a delegate object and add A::F to its invocation list
MyF ^d=gcnew MyF(&A::F);
d+=gcnew MyF(a, &A::G);//add A::G to invocation list

This feature seems cute and harmless, and I’m not going to say that isn’t except that in C++ binders and tr1::function do the same job, in a more efficient and less verbose manner. Furthermore, forcing delegates to be GC-bound seems a questionable design decision. If delegates are such a fundamental feature, it would have been better to make them value objects. Finally, delegates expose the Windows-biased nature of C++/CLI.

Namespaces

C++/CLI namespaces are an odd beast. They are misleadingly called namespaces, but in reality they are much closer to Java’s packages. So why aren’t they called packages? Historically, C# designers tried to impart the impression they were creating a language that was completely independent of Java, for legal and marketing reasons. Therefore, features borrowed from Java were renamed.

C++/CLI borrowed its namespaces from C#, not C++. However, it uses term namespaces not just for historical reasons but probably because it might suggest that C++/CLI and ISO C++ are closer than they truly are. The ECMA standard as usual doesn’t disclose too much information about the syntax and the semantics. However, the scanty examples it does provide will convince you that C++/CLI namespaces are not the namespaces you know. Notice also that in the example cited below (with minor modifications), the file extension is .cpp. I wonder how many ISO compliant C++ compilers will accept this code:

//DisplayMessageLibrary.cpp
namespace MyLibrary
{
 public ref struct DisplayMessage
 {
 static void Display();
 };
}

//DisplayMessageApp.cpp
using <DisplayMessageLibrary.dll>
int main()
{
 MyLibrary::DiaplayMessage::Display();
}

You’ve never seen something like this in ISO C++, have you? C++/CLI namespaces are used for loading assemblies, or in a more down to earth example, they are the syntactic sugar that hides the onerous Win32 API call:

LoadLibrary("DisplayMessageLibrary.dll");

Once more, it’s obvious that while C++/CLI pretends to be a general purpose, platform neutral language, it really is a Windows-only game.

Generics

When C++ programmers speak of generic programming, they mean template-based programming. However, in the "newer programming languages" generics mean something quite different. C++/CLI Generics are instantiated by the Virtual Execution System (the .Net equivalent of Java’s JVM) at runtime, whereas C++ templates are instantiated at compile-time. Performance-wise, generics are slower than templates by magnitudes. Considering the fat interface of a typical C++/CLI object and the dynamic nature of this language, it’s even slower than you think. Furthermore, since C++/CLI has a unified type system whereby every type is ultimately derived from System::Object (surprise!) generics are ultimately functions and classes that operate on references to System::Object objects, even when you apply them to built-in types such as int. To recover the actual type from a generic class or function, C++/CLI uses runtime type identification and conversions. C++/CLI support constraints -- a feature that may look tempting at first, until you discover that they are enforced at runtime. I will not go through all the gory details here, but the bottom line is that a C++/CLI generic container is a performance killer compared to a homogeneous C++ container such as std::vector<int>. To get a hint of the overhead, think of a homogeneous container in C++ implemented as a collection of pointers whose dynamic types have to be recovered at runtime. While in certain cases this overhead is inevitable (i.e., when you really need a heterogeneous container), most of the time you need a homogeneous container, with no dynamic typing whatsoever.

My prediction is that in the next 10 years, we’ll be hearing miraculous reports about a performance breakthrough of the new VES, quantum leap optimizations of generics, and carefully doctored benchmark results that "prove" that C++/CLI code performs better than native C++ code.

What about real templates? The ECMA standard says that C++/CLI supports them as well. However, it’s underspecified as ever with respect to template subtleties. For instance, is it possible to provide default parameter types? What about member templates? Are they permitted only in native classes or can a ref class have them too? Answers on postcards please.

Conclusions

I believe that by now you are convinced as I am that C++/CLI is neither a "set of extensions to C++" (in many aspects it’s actually a subset of C++), nor is it related to C++ more than any other language with semicolons and curly braces. Furthermore, C++/CLI is definitely a Windows-oriented programming language; it’s definitely not a language that a Solaris 10 server or a Nokia mobile phone will be happy to run. What does it have anything to do with C++?

When C# was launched six years ago, I asked "why does the world need another proprietary language?" C# has certainly remained a proprietary language. The only thing that has changed is that now we have two proprietary languages from the same vendor. Wouldn’t it have been a better idea to finish the C# overhaul first instead of spawning yet another language that looks surprisingly similar and suffers from the same imperfections? While Microsoft is free to exert its marketing strategies and ruses (including the anointment of a new proprietary language every once in awhile) C++/CLI marks a dangerous move. It tries to hijack a well-designed and reputable programming language that has stood the test of time more than any other language owned by commercial bodies. I’m truly concerned that interested parties might tamper with ISO C++, trying to push spurious and unnecessary C++/CLI features into it in order to make it "more compatible" with their implementation.

As far as ECMA is concerned, there’s no nice way to put it: if it has ratified this underspecified and incoherent draft as an International Standard, it says quite a lot about the credibility, reputation and expertise of this body. I wouldn’t buy a used standard from them.

In terms of its engineering merits, C++/CLI fails to impress me. I can’t think of a single feature thereof (except perhaps the nullptr keyword) that I would like to see added to ISO C++. On the other hand, I’m quite surprised that it repeats so many of the design mistakes previously seen in Java and C#.

Education wise, there’s no arguing that Java and C# have become complex and hard to learn. Yet C++/CLI is even worse. Much worse. In many areas, it has a dual or triple interface that is the result of an unsuccessful attempt to combine C++ concepts with the dynamic (so-called "managed") nature of C#. Think of the notion of ^ pointers as opposed to native * pointers, generics versus templates, finalizers versus destructors -- and this is just the first version of this language!

I’m happy and proud to be using ISO C++ -- perhaps more than ever before. In every aspect, I find it a mature, well-balanced and ingeniously designed language. My only hope it remains true to its nature: a general purpose, platform neutral, efficient, multi-paradigm language with the best generic facilities and libraries ever designed. In this respect, the grass isn’t greener over there.