This is a chapter excerpt from C++/CLI in Action authored by Nishant Sivakumar
and published by Manning Publications. The content has been reformatted for
CodeProject and may differ in layout from the printed book and the e-book.

1.3 Declaring CLR types

In this section, we'll look at the syntax for declaring CLI (or CLR) types, modifiers
that can be applied to CLI types, and how CLI types implement inheritance. C++/
CLI supports both native (unmanaged) and managed types and uses a consistent
syntax for declaring various types. Native types are declared and used just as they
are in standard C++. Declaring a CLI type is similar to declaring a native type,
except that an adjective is prefixed to the class declaration that indicates the type
being declared. Table 1.3 shows examples of CLI type declarations for various types.

Table 1.3 Type declaration syntax for CLI types

C# developers may be a little confused by the usage of both classand struct for both reference and value types. In C++/CLI,
struct and class can be used interchangeably (just as
in standard C++), and they follow standard C++ visibility rules for structs and
classes. In a class, methods are private by default; in a struct,
methods are public by default. In table 1.3, RefClass1::Func
and ValClass1::Func are both private, whereas
RefClass2::Func and ValClass2::Func are both public.
For the sake of clarity and consistency with C#, you may want to exclusively use
refclass for ref types and value struct
for value types instead of mixing class and struct for
both ref and value types.

Interface methods are always public; declaring an interface as a
struct is equivalent to declaring it as a class. This means IType1::Func
and IType2::Func are both public in the
generated MSIL. C# developers must keep the following in mind:

A C++/CLI value class (or value struct) is the same as a C# struct.

A C++/CLI ref class (or ref struct) is the same as a C# class.

Those of you who have worked on the old MC++ syntax should remember these
three points:

A ref class is the same as an __gc class.

A value class is the same as an __valueclass.

An interface class is the same as an __interface.

Spaced keywords

An interesting thing that you need to be aware of is that only three
new, reserved keywords have been introduced in C++/CLI: gcnew,
nullptr,
and generic. All the other seemingly new keywords are spaced (or
contextual) keywords. Syntactic phrases like refclass,
foreach, and
value classare spaced keywords that are treated as single tokens in the
compiler's lexical analyzer. The big advantage is that any existing code
that uses these new keywords (like ref or each) continues to compile
correctly, because it's not legal in C++ to use a space in an
identifier. The following code is perfectly valid in C++/CLI:

intref = 0;
intvalue = ref;
booleach = value == ref;

Of course, if your existing code uses gcnew, nullptr, or
generic as
an identifier, C++/CLI won't compile it, and you'll have to rename those
identifiers.

You've seen how CLI types can be declared. Next, you'll see how type
modifiers can be applied to these classes (or structs, as the case may be).

1.3.1 Class modifiers

You can specify the abstractand sealed modifiers
on classes; a class can be marked both abstract and sealed.
But such classes can't be derived explicitly from any base class and can only
contain static members. Because global functions aren't CLS-compliant,
you should use abstractsealedclasses with static functions,
instead of global functions, if you want your code to be CLS-compliant.

In case you're wondering when and why you would need to use these modifiers,
remember that, to effectively write code targeting the .NET Framework, you
should be able to implement every supported CLI paradigm. The CLI explicitly
supports abstract classes, sealed classes, and classes that are both abstract
and sealed. If the CLI supports it, you should be able to do so, too.

Just as with standard C++, an abstract class can only be used as a base class
for other classes. It isn't required that the class contains abstract methods
for it to be declared as an abstract class, which gives you extra flexibility
when designing your class hierarchy. The following class is abstract because
it's declared abstract, although it doesn't contain any abstract
methods:

refclass R2 abstract
{
public:
virtualvoid Func(){}
};

An interesting compiler behavior is that if you have a class with an abstract
method that isn't marked abstract, such as the following class, the
compiler issues warning C4570 (class is not explicitly declared as abstract
but has abstract functions) instead of issuing an error:

refclass R1
{
public:
virtualvoid Func() abstract;
};

In the generated IL, the class R1 is marked abstract, which means that if you
try to instantiate the class, you'll get a compiler error (and you should). Not
marking a class abstract when it has abstract methods is untidy, and I strongly
encourage you to explicitly mark classes abstract if at least one of their
methods is abstract. Note how I've used the abstract modifier on a class method
in the previous example; you'll see more on this and other function modifiers in
chapter 2.

Using the sealed modifier follows a similar syntax. A sealed class can't be used as a base class for any other class—it seals
the class from further derivation:

Sealed classes are typically used when you don't want the characteristics of
a specific class to be modified (through a derived class), because you want to
ensure that all instances of that class behave in a fixed manner. Because a
derived class can be used anywhere the base class can be used, if you allow your
class to be inherited from, by using instances of the derived class where the
base class instance is expected, users of your code can alter the expected
functionality (which you want to remain unchangeable) of the class. For example,
consider a banking application that has a CreditCardInfo class that
is used to fetch information about an account holder's credit-card transactions.
Because instances of this class will be occasionally transmitted across the
Internet, all internal data is securely stored using a strong encryption
algorithm. By allowing the class to be inherited from, there is the risk of an
injudicious programmer forgetting to properly follow the data encryption
implemented by the CreditCardInfo class; thus any instance of the
derived class is inherently insecure. By marking the CreditCardInfo
class as sealed, such a contingency can be easily avoided.

A performance benefit with using a sealed class is that, because the compiler
knows a sealed class can't have any derived classes, it can statically resolve
virtual member invocations on a sealed class instance using nonvirtual
invocations. For example, assuming that the CreditCardInfo class
overrides the GetHashCode method (which it inherits from
Object), when you call GetHashCode at runtime, the CLR
doesn't have to figure out which function to call. This is the case because it
doesn't have to determine the polymorphic type of the class (because a
CreditCardInfo object can only be a CreditCardInfo object,
it can't be an object of a derived type—there are no derived types). It directly
calls the GetHashCode method defined by the CreditCardInfo
class.

As mentioned earlier, abstractsealedclasses can't have
instance methods; attempting to include them will throw compiler error C4693.
This isn't puzzling when you consider that an instance method on an abstractsealedclass would be worthless, because you can never have an
instance of such a class. An abstractsealedclass can't be
explicitly derived from a base class, although it implicitly derives from
System::Object. For those of you who've used C#, it may be interesting to
know that an abstractsealedclass is the same as a C# static
class.

Now that we've discussed how to declare CLI types and apply modifiers on
them, let's look at how CLI types work with inheritance.

1.3.2 CLI types and inheritance

Inheritance rules are similar to those in standard C++, but there are
differences, and it's important to realize what they are when using C++/CLI. The
good thing is that most of the differences are obvious and natural ones dictated
by the nature of the CLI. Consequently, you won't find it particularly strenuous
to remember them.

Reference types (refclass/struct) only support public inheritance, and if
you skip the access keyword, public inheritance is assumed:

refclass Base
{
};
refclass Derived : Base // implicitly public
{
};

If you attempt to use private or protected inheritance, you'll get compiler
error C3628. The same rule applies when you implement an interface; interfaces
must be implemented using public inheritance, and if you skip the access
keyword, public is assumed:

The rules for value types and inheritance are slightly different from those
for ref types. A value type can only implement interfaces; it can't inherit from
another value or ref type. That's because value types are implicitly derived
from System::ValueType. Because CLI types don't support multiple base classes,
value types can't have any other base class. In addition, value types are always
sealed and can't be used as base classes. In the following code snippet, only
the Derived3 class compiles. The other two classes attempt to inherit from a ref
class and a value class, neither of which is permitted:

These restrictions are placed on value types because value types are intended
to be simple types without the complexities of inheritance or referential
identity, which can be implemented using basic copy-by-value semantics. Also
note that these restrictions are imposed by the CLI and not by the C++ compiler.
The C++ compiler merely complies with the CLI rules for value types. As a
developer, you need to keep these restrictions in mind when designing your
types. Value types are kept simple to allow the CLR to optimize them at runtime
where they're treated like simple plain old data (POD) types like an int or a
char, thus making them extremely efficient compared to reference types.

Here's a simple rule you can follow when you want to decide whether a class
should be a value type: Try to determine if you want it to be treated as a class
or as plain data. If you want it to be treated as a class, don't make it a value
type; but if you want it to behave just as an int or a char would, chances are
good that your best option is to declare it as a value type. Typically, you'll
want it to be treated as a class if you expect it to support virtual methods,
user-defined constructors, and other aspects characteristic of a complex data
type. On the other hand, if it's just a class or a struct with some data members
that are themselves value types, such as an int or char, you may want to make
that a value type.

One important point to be aware of is that CLI types don't support multiple
inheritance. So, although a CLI type can implement any number of interfaces, it
can have only one immediate parent type; if none is specified, this is
implicitly assumed to be System::Object.

Next, we'll talk about one of the most important features that has been
introduced in VC++ 2005: the concept of handles.

1.4 Handles: The CLI equivalent to pointers

Handles are a new concept introduced in C++/CLI; they replace the __gc
pointer concept used in Managed C++. Earlier in the chapter, we discussed the
pointer-usage confusion that prevailed in the old syntax. Handles solve that
confusion. In my opinion, the concept of handles has contributed the most in
escalating C++ as a first-class citizen of the .NET programming language world.
In this section, we'll look at the syntax for using handles. We'll also cover
the related topic of using tracking references.

1.4.1 Syntax for using handles

A handle is a reference to a managed object on the CLI heap and is
represented by the ^ punctuator (pronounced hat).

NOTE

When I say punctuator in this chapter, I'm talking from a compiler
perspective. As far as the language syntax is concerned, you can replace
the word punctuator with operator and retain the same meaning.

Handles are to the CLI heap what native pointers are to the native C++ heap;
and just as you use pointers with heap-allocated native objects, you use handles
with managed objects allocated on the CLI heap. Be aware that although native
pointers need not always necessarily point to the native heap (you could get a
native pointer pointing to the managed heap or to non-C++ allocated memory
storage), managed handles have a close-knit relationship with the managed heap.
The following code snippet shows how handles can be declared and used:

In the code, str is a handle to a System::String
object on the CLI heap, student is a handle to a Student
object, and SelectSubject invokes a method on the
student handle.

The memory address that str refers to isn't guaranteed to remain
constant. The String object may be moved around after a
garbage-collection cycle, but str will continue to be a reference
to the same System::String object (unless it's programmatically
changed). This ability of a handle to change its internal memory address when
the object it has a reference to is moved around on the CLI heap is called tracking.

Handles may look deceitfully similar to pointers, but they are totally
different entities when it comes to behavior. Table 1.4 illustrates the
differences between handles and pointers.

Handles

Pointers

Handles are denoted by the ^ punctuator.

Pointers are denoted by the * punctuator.

Handles are references to managed objects on the CLI heap.

Pointers point to memory addresses.

Handles may refer to different memory locations throughout their
lifetime, depending on GC cycles and heap compactions.

Pointers are stable, and garbage-collection cycles don't affect
them.

Handles track objects, so if the object is moved around, the handle
still has a reference to that object.

If an object pointed to by a native pointer is programmatically
moved around, the pointer isn't updated.

Handles are type-safe.

Pointers weren't designed for type-safety.

The gcnew operator returns a handle to the instantiated
CLI object.

The new operator returns a pointer to the instantiated
native object on the native heap.

It isn't mandatory to delete handles. The Garbage Collector
eventually cleans up all orphaned managed objects.

It's your responsibility to call delete on pointers to objects that
you've allocated; if you don't do so, you'll suffer a memory leak.

Handles can't be converted to and from a void^.

Pointers can convert to and from a void*.

Handles don't allow handle arithmetic.

Pointer arithmetic is a popular mechanism to manipulate native data,
especially arrays.

Table 1.4 Differences between handles and pointers

Despite all those differences, typically you'll find that for most purposes,
you'll end up using handles much the same way you would use pointers. In fact,
the * and -> operators are used to dereference a
handle (just as with a pointer). But it's important to be aware of the
differences between handles and pointers. The VC++ team members initially called
them managed pointers, GC pointers, and tracking pointers. Eventually, the team
decided to call them handles to avoid confusion with pointers; in my opinion,
that was a smart decision.

Now that we've covered handles, it's time to introduce the associated concept
of tracking references.

1.4.2 Tracking references

Just as standard C++ supports references (using the &
punctuator) to complement pointers, C++/CLI supports tracking references that
use the % punctuator to complement handles. The standard C++
reference obviously can't be used with a managed object on the CLR heap, because
it's not guaranteed to remain in the same memory address for any period of time.
The tracking reference had to be introduced; and, as the name suggests, it
tracks a managed object on the CLR heap. Even if the object is moved around by
the GC, the tracking reference will still hold a reference to it. Just as a
native reference can bind to an l-value, a tracking reference can bind to a
managed l-value. And interestingly, by virtue of the fact that an l-value
implicitly converts to a managed l-value, a tracking reference can bind to
native pointers and class types, too. Let's look at a function that accepts a
String^ argument and then assigns a string to it. The first version
doesn't work as expected; the calling code finds that the String
object it passed to the function hasn't been changed:

If you execute this code snippet, you'll see that str contains
the old string after the call to ChangeString. Change
ChangeString to:

void ChangeString(String^% str)
{
str = "New string";
}

You'll now see that str does get changed, because the function
takes a tracking reference to a handle to a String object instead
of a String object, as in the previous case. A generic definition
would be to say that for any type T, T% is a tracking
reference to type T. C# developers may be interested to know that
MSIL-wise, this is equivalent to passing the String as a C# ref argument to ChangeString. Therefore, whenever you want
to pass a CLI handle to a function, and you expect the handle itself to be
changed within the function, you need to pass a tracking reference to the handle
to the function.

In standard C++, in addition to its use in denoting a reference, the &
symbol is also used as a unary address-of operator. To keep things uniform, in
C++/CLI, the unary % operator returns a handle to its operand, such
that the type of %T is T^ (handle to type T).
If you plan to use stack semantics (which we'll discuss in the next chapter),
you'll find yourself applying the unary % operator quite a bit when
you access the .NET Framework libraries. This is because the .NET libraries
always expect a handle to an object (because C++ is the only language that
supports a non-handle reference type); so, if you have an object declared using
stack semantics, you can apply the unary % operator on it to get a
handle type that you can pass to the library function. Here's some code showing
how to use the unary % operator:

Be aware that the * punctuator is used to dereference both
pointers and handles, although symmetrically thinking, a ^
punctuator should been used to dereference a handle. Perhaps this was designed
this way to allow us to write agnostic template/generic classes that work on
both native and unmanaged types.

You now know how to declare a CLI type; you also know how to use handles to a
CLI type. To put these skills to use, you must understand how CLI types are
instantiated, which is what we'll discuss in the next section.

Share

About the Author

Nish Nishant is a Software Architect/Consultant based out of Columbus, Ohio. He has over 15 years of software industry experience in various roles including Lead Software Architect, Principal Software Engineer, and Product Manager. Nish is a recipient of the annual Microsoft Visual C++ MVP Award since 2002 (13 consecutive awards as of 2014).

Nish is an industry acknowledged expert in the Microsoft technology stack. He authored C++/CLI in Action for Manning Publications in 2005, and had previously co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is vastly experienced in team management, mentoring teams, and directing all stages of software development.