In this chapter, you’ll see what C++/CLI can be used for and how C++/CLI
improves the now-obsolete Managed C++ syntax. We’ll also go over basic C++/
CLI syntax. By the end of this chapter, you’ll know how to write and compile a
C++/CLI program and how to declare and use managed types. Some of the new
syntactic features may take a little getting used to, but C++ as a language has
never had simplicity as its primary design concern.
When C++ was wedded to CLI with a slash, it was apparent from the beginning
that it wasn’t going to be a celebrity marriage. The world’s most powerful high
level programming language—C++—was given a face-lift so that it could be used
to develop on what could potentially be the world’s most popular runtime environment:
the CLI.
In this chapter, you’ll see what C++/CLI can be used for and how C++/CLI
improves the now-obsolete Managed C++ syntax. We’ll also go over basic C++/
CLI syntax. By the end of this chapter, you’ll know how to write and compile a
C++/CLI program and how to declare and use managed types. Some of the new
syntactic features may take a little getting used to, but C++ as a language has
never had simplicity as its primary design concern. Once you get used to it, you
can harness the power and ingenuity of the language and put that to effective use.
1.1 THE ROLE OF C++/CLI
C++ is a versatile programming language with a substantial number of features
that makes it the most powerful and flexible coding tool for a professional developer.
The Common Language Infrastructure (CLI) is an architecture that supports
a dynamic language-independent programming model based on a Virtual
Execution System. The most popular implementation of the CLI is Microsoft’s
.NET Framework for the Windows operating system. C++/CLI is a binding
between the standard C++ programming language and the CLI. Figure 1.1 shows
the relationship between standard C++ and the CLI.
C++ has been paired with language extensions before, and the result hasn’t
always been pretty. Visual C++ 2005 is the first version of a Microsoft C++ compiler
that has implemented the C++/CLI specification. This means three things
for the C++ developer:
C++ can be used to write applications that run on the .NET Framework.
There is no need to learn a totally new language or to abandon all the
C++ knowledge and experience built up through years of coding.
C++/CLI lets developers reuse their native C++ code base, saving the
agony of having to rewrite all the existing code to enable it to run on the
.NET Framework.
C++/CLI is designed to be the lowest-level language for the .NET Framework.
For writing purely managed applications, it’s your most powerful
choice; or, as I like to say, "C++/CLI actually lets you smell the CLR (Common
Language Runtime)."
Visual C++ 2005 isn’t Microsoft’s first attempt at providing a C++ compiler
capable of targeting managed code. Both VC++ 2002 and VC++ 2003 featured a
C++ compiler that supported the managed extensions to C++ (referred to as
Managed C++ or MC++). As a syntactic extension, it would be an understatement
to say that it was a comprehensive failure.
Now that you understand the role of C++/CLI, let’s examine why it’s such an
invaluable inclusion among CLI languages.
The .NET Framework
It’s important for you to have a basic understanding of the .NET Framework,
because although this book will teach you the C++/CLI syntax before we move on
to various interop mechanisms and strategies, it doesn’t attempt to teach you the
core details of the .NET Framework. If you’ve never used the .NET Framework, you
should read the book’s appendix ("A Concise Introduction to the .NET Framework")
before you proceed further. On the other hand, if you’ve previously worked with the
.NET Framework, you can refresh your memory by looking at these quick definitions
(in no particular order) of various terms associated with the .NET Framework, which
you’ll encounter in this and other chapters:
.NET Framework: The .NET Framework is Microsoft’s implementation of the
Common Language Infrastructure (CLI), which itself is an open specification
that has been standardized by the ECMA (an international standards body).
The .NET Framework consists of the Common Language Runtime (CLR) and
the Base Class Library (BCL).
CLR: The Common Language Runtime is the core of the .NET Framework and
implements the fundamental aspects of the CLI such as the Virtual Execution
System (VES), the Garbage Collector, and the Just in Time (JIT) compiler.
BCL: The Base Class Library is an extensive set of .NET classes that is used
by any .NET language (such as C#, VB.NET, C++/CLI, and so on).
VES: The Virtual Execution System is the engine responsible for executing
managed code, including the invocation of the Garbage Collector as well as
the JIT compiler.
Garbage Collector: Memory management is automatically done in the .NET
Framework. The CLR includes a Garbage Collector that frees resources when
they’re no longer needed, so the developer doesn’t need to worry about that.
JIT compiler: .NET compilers (C#, VB.NET, C++/CLI, and so on) compile
source code into an intermediate language called Microsoft Intermediate Language
(MSIL). At runtime, the CLR uses the JIT compiler to compile this MSIL
into the native code for the underlying operating system before executing it.
CTS: The Common Type System (CTS) is a set of rules that specifies how the
CLR can define, use, create, and manage types.
CLS: The Common Language Specification (CLS) is a subset of the CTS that
all languages must implement if they’re to be considered CLS-compliant. CLScompliant
languages can interop with each other as long as they don’t use any
non-CLS-compliant features present in their specific compiler version.
I’d like to reiterate that if you’re not familiar with these terms, or if you wish to
understand them in more detail, please take a detour into the appendix, where
most of these concepts are explained more thoroughly.½
1.1.1 What C++/CLI can do for you
If you’re reading this book, chances are good that you’re looking to move your
applications to the .NET Framework. The biggest concern C++ developers have
about making the move to .NET is that they’re afraid of abandoning their existing
native code and having to rewrite everything to managed code. That’s exactly
where C++/CLI comes into the picture. You do not have to abandon your current
native code, nor do you have to rewrite everything to managed code. That’s C++/
CLI’s single biggest advantage—the ability to reuse existing native code.
Reuse existing native code
Visual C++ 2005 allows you to compile your entire native code base to MSIL
with the flick of a single compilation switch. In practice, you may find that you
have to change a small percentage of your code to successfully compile and
build your applications. This is a far better option than either abandoning all
your code or rewriting it entirely. Once you’ve successfully compiled your code
for the CLR, the code can access the thousands of classes available in the .NET
Base Class Library.
Access the entire .NET library
The .NET Framework comes with a colossal library containing thousands of
classes that simplify your most common developmental requirements. There are
classes relating to XML, cryptography, graphical user interfaces, database technologies,
OS functionality, networking, text processing, and just about anything
you can think of. Once you’ve taken your native applications and compiled them
for the CLR, you can use these .NET classes directly from your code. For example,
you can take a regular MFC dialog-based application and give it some encryption
functionality using the .NET cryptography classes. You aren’t restricted to managed
libraries, because C++/CLI lets you seamlessly interop between managed
and native code.
Most powerful language for interop
Although other languages like C# and VB.NET have interop features, C++/CLI
offers the most powerful and convenient interop functionality of any CLI language.
C++/CLI understands managed types as well as native types; consequently,
you often end up using whatever library you want (whether it’s a native
DLL or a managed assembly) without having to worry about managed/native type
conversions. Using a native library from C++/CLI is as simple as #include-ing the
required header files, linking with the right lib files, and making your API or class
calls as you would normally do. Compare that with C# or VB.NET, where you’re
forced to copy and paste numerous P/Invoke declarations before you can access
native code. In short, for any sort of interop scenario, C++/CLI should be an
automatic language choice. One popular use of interop is to access new managed
frameworks such as Windows Forms from existing native applications. (Note that
this technique is covered in detail in part 3 of the book.)
Leverage the latest managed frameworks
Imagine that you have a substantially large MFC application and that your company
wants to give it a new look and feel. You’ve recently acquired an outstanding
Windows Forms–based UI library from another company. Take VC++ 2005,
recompile the MFC application for the CLR, and change the UI layer to use
the Windows Forms library, and now you have the same application that uses the
same underlying business logic with the new shiny user interface. You aren’t
restricted to Windows Forms or even to UI frameworks. The next version of Windows
(called Windows Vista) will introduce a new UI framework called the Windows
Presentation Foundation (WPF). It’s a managed framework and C++/CLI will
let you access it from existing native applications. When Vista is released, your
applications will be able to flaunt the WPF look and feel. Note that WPF is also
being made available for Windows XP, so you aren’t restricted to using Vista to run
WPF-based applications.
Another powerful managed framework that is coming out in Vista is the Windows
Communication Foundation (WCF), which, as the name implies, is a powerful
communication framework written in managed code. And yes, although you knew
I was going to say it, you can access the WCF from your Visual C++ applications.
Although native code reuse and powerful interop are its most popular advantages,
C++/CLI is also your most powerful option to write managed applications.
Write powerful managed applications
When Brandon Bray from the Visual C++ Compiler team said that C++/CLI
would be the lowest-level language outside of MSIL, he meant what he said! C++/
CLI supports more MSIL features than any other CLI language; it is to MSIL what
C used to be to Assembly Language in the old days. C++/CLI is currently the only
CLI language that supports stack semantics and deterministic destruction, mixed
types, managed templates, and STL.NET (a managed implementation of the
Standard Template Library).
A natural question you may have now is why Microsoft introduced a new syntax.
Why didn’t it continue to use the old MC++ syntax? That’s what we examine next.
1.1.2 The rationale behind the new syntax
The managed extensions to C++ introduced in VC++ 2002 were not well
received by the C++ developer community. Most people appreciated the fact that
they could use C++ for .NET development, but almost everybody thought the
syntax was gratuitously twisted and unnatural, that the managed and unmanaged
pointer usage semantics were confusing, and that C++ hadn’t been given equal
footing as a CLI language with other languages like C# or VB.NET. Another factor
that contributed to poor Managed C++ acceptance was the fact that designer
support for Windows Forms was not available in the 2002 release; although
the 2003 release did introduce a designer, it wasn’t as stable or functional as the
designers available for C# and VB.NET, and creating pure managed applications
was dreadfully unfeasible with C++.
Microsoft took the feedback from its C++ developer community seriously,
and on October 6, 2003, the ECMA (an association dedicated to the standardization
of Information and Communication Technology and Consumer Electronics)
announced the creation of a new task group to oversee the development
of a standard set of language extensions to create a binding between the ISO
standard C++ programming language and the CLI. Microsoft developed and
submitted full draft specifications for binding the C++ Programming Language
to the Common Language Infrastructure in November 2003, and C++/CLI
became an international ECMA standard in December 2005. It was expected that
the ECMA would submit it to the ISO for consideration as a potential ISO
standard. Visual C++ 2005 is the first publicly available compiler to support this
new standard.
NOTE: The ECMA (the acronym originally stood for European Computer Manufacturers
Association) is an association founded in 1961 that’s dedicated
to the standardization of Information Technology systems. The
ECMA has close liaisons with other technology standards organizations
and is responsible for maintaining and publishing various standards
documents. Note that the old acronym isn’t used anymore, and the body
today goes by the name ECMA International. You can visit its website at
www.ecma-international.org.
Let’s take a quick look at a few of the problems that existed in the old syntax and
how C++/CLI improves on these issues. If you’ve used the old syntax in the past,
you’ll definitely appreciate the enhancements in the new syntax; if you haven’t,
you’ll still notice the stark difference in elegance between the two syntaxes.
Twisted syntax and grammar
The old Managed C++ syntax used a lot of underscored keywords that were
clunky and awkward. Note that these double-underscored keywords were required
to conform to ANSI standards, which dictate that all compiler-specific keywords
need to be prefixed with double underscores. But as a developer, you always want
your code to feel natural and elegant. As long as developers felt that the code
they wrote didn’t look or feel like C++, they weren’t going to feel comfortable
using that syntax; most C++ developers chose not to use a syntax that
felt awkward.
C++/CLI introduced a new syntax that fit in with existing C++ semantics. The
elegant grammar gives a natural feel for C++ developers and allows a smooth
transition from native coding to managed coding.
Look at Table 1.1, which compares the old and new syntaxes; you’ll see what
I mean.
Even without knowing the rules for either the old or the new syntax or C++/
CLI, you shouldn’t find it hard to decide which is the more elegant and natural of
the two. Don’t worry if the code doesn’t make a lot of sense to you right now. Later
in this chapter, we’ll go through the fundamental syntactic concepts of the C++/
CLI language. I just wanted to show you why the old syntax never became popular
and how Microsoft has improved on the look and feel of the syntax in the new
C++/CLI specification.
With the old syntax, every time you used a CLI feature, such as a delegate or
a property, you had to prefix it with underscored keywords. For a property definition,
the old syntax required separate setter and getter blocks and didn’t syntactically
organize them into a single block. This meant that if you carelessly
separated the getter and setter methods with other code, there was no visual cue
that they were part of the same property definition. With the new syntax, you
put your getter and setter functions inside a property block; the relationship
between them is visually maintained. To summarize my personal thoughts on
this issue, with the old syntax, you feel that you’re using two unrelated sublanguages
(one for managed code and one for native code) with a single compiler.
With the new syntax, you feel that you’re using C++, albeit with a lot of new keywords:
It’s still a single language. Note that the VC++ team made an effort to
ensure that the new keywords don’t interfere with existing code bases, as you’ll
see later in the book.
Programmers can be compiler snobs. Many developers opined that C# and
VB.NET were proper .NET languages, whereas MC++ was a second-class citizen
compared to them. Let’s see what has been done to the C++ language to promote
it to a first-class CLI status.
Second class CLI support
Managed C++ seemed like a second-class CLI language when compared to languages
like C# and VB.NET, and developers using it had to resort to contorted
workarounds to implement CLI functionality. Take a trivial example such as enumerating
over the contents of an ArrayList object. Here’s what the code would
look like in Managed C++:
While languages like C# provided a for each construct that abstracted the entire
enumeration process, C++ developers were forced to access the IEnumerator for
the ArrayList object and use that directly—not a good thing, as far as Objected-
Oriented abstraction rules were concerned. Now the programmer needs to know
that the collection has an enumerator, that the enumerator has a MoveNext
method and a Current property, and that they have to repeatedly call MoveNext
until it returns false. This information should be hidden from the programmer.
Requiring the internal implementation details of the collection to be directly
used defeats the purpose of having collection classes, when the reason for having
them is to abstract the internal details of an enumerable collection class from
the programmer.
Look at the equivalent C++/CLI code:
for each(String^ s in arraylist)
{
Console::WriteLine(s);
}
By adding constructs such as for each, which give developers a more natural syntax
to access .NET features, the VC++ team has given us a cozy feeling that C++/
CLI is now a first-class language for .NET programming. Later in this chapter,
you’ll see that boxing is now implicit, which means you don’t have to use the gratuitous
__box keyword required in the old syntax. If you don’t know what boxing
means, don’t worry; it will be explained when we talk about boxing and unboxing.
Poor integration of C++ and .NET
One major complaint about Managed C++ was that C++ features such as templates
and deterministic destruction weren’t available. Most C++ developers felt
severely handicapped by the apparent feature reductions when using MC++.
With C++/CLI, templates are supported on both managed and unmanaged
types. In addition, C++/CLI is the only CLI language that supports stack semantics
and deterministic destruction (although languages like C# 2.0 use indirect
workarounds like the using-block construct to conjure up a form of deterministic
destruction).
A crisp summarization would be to say that C++/CLI bridges the gap between
C++ and .NET by bringing C++ features such as templates and deterministic
destruction to .NET, and .NET features like properties, delegates, garbage collection,
and generics to C++.
Confusing pointer usage
Managed C++ used the same * punctuator-based operator syntax for unmanaged
pointers into the C++ heap and managed references into the CLI heap. Not
only was this confusing and error-prone, but managed references were different
entities with totally different behavioral patterns from unmanaged pointers. Consider
the following code snippet:
__gc class R
{
};
class N
{
};
. . .
N* pN = new N();
R* pR = new R();
The two calls to new (shown in bold) do completely different things. The new call
on the native class N results in the C++ new operator being called, whereas the new
call on the managed class R is compiled into the MSIL newobj instruction. The
native object is allocated on the C++ heap, but the managed object is allocated
on the garbage-collected CLR heap, which has the side implication that the memory
address for the object may change every time there is a garbage-collection
cycle or a heap-compaction operation. The R* object looks like a native C++
pointer, but it doesn’t behave like one, and its address can’t be assumed to remain
fixed. The good news is that this issue has been fixed in C++/CLI. We now have
an additional gcnew keyword for instantiating managed objects. We also have the
concept of a handle (as opposed to a pointer) to a managed object that uses the ^
punctuator (instead of *) as the handle operator. Later in this chapter, we’ll take a
more detailed look at handles and the gcnew operator. For now, it should suffice to
note that in C++/CLI, there will be no managed/unmanaged pointer confusion.
Unverifiable code
The Managed C++ compiler could not produce verifiable code, which meant that
you couldn’t use it to write code that was to run under a protected environment—
such as an SQL Server-stored procedure. Visual C++ 2005 supports a special compiler
mode (/clr:safe) that produces verifiable code and disallows you from
compiling any non-verifiable code by generating errors during compilation. The
advantage of being able to create verifiable assemblies is that the CLR can enforce
active CLR security restrictions on the running application. This gives you a wider
scope to deploy your applications (for example, as SQL Server components) in
secure environments like those in a banking system and in future Windows
releases where code may have to be verifiable to be permitted to execute.
It should be obvious by now why Microsoft decided to bring out a new syntax.
If you’ve never used the old syntax, you can consider yourself lucky that you can
now use the powerful new C++/CLI language to write managed applications.
If you have used the old syntax, I strongly recommend spending some time
porting the old syntax code to the new syntax as early as possible. The old syntax
support (available in VC++ 2005 through the /clr:oldSyntax compiler switch)
isn’t guaranteed to be available in future VC++ versions, nor will any significant
improvements be made to it. Let’s now move on to our first C++/CLI program.
1.2 HELLO WORLD IN C++/CLI
Before we go any further, let’s write our first Hello World application in C++/CLI.
In this section, we’ll also look at the new compiler options that have been introduced
in VC++ 2005 to support compilation for managed code. There is nothing
overly-complicated about the code in Listing 1.1, but for our purposes, it does
nicely to illustrate a few basic language concepts of C++/CLI.
You can compile this code from the command line using the C++ compiler cl.exe
as follows:
cl /clr First.cpp
Run it, and it promptly displays "Hello" followed by the current user (most likely
your Windows login name) on the console. Except for the gcnew keyword (which
we’ll talk about later in this chapter), it doesn’t look very different from a regular
C++ program, does it? But the executable that has been created is a .NET executable
that runs on the .NET Common Language Runtime. When I say .NET executable,
I mean an MSIL program that is JIT compiled and executed by the CLR just
like any executable you might create using C#, VB.NET, or another CLI language.
This is a small example, but it communicates two important facts: You can use
familiar C++ syntax to write .NET applications, thereby avoiding the need to
learn a new language like C# or VB.NET; and you can write managed and
native code within the same application.
For those of you who aren’t familiar with the .NET Framework, Console is a
.NET Framework BCL class that belongs to the System namespace (hence the
using namespace declaration on top), and WriteLine is a static method of the Console
class. Listing 1.1 uses native data types like TCHAR and DWORD as well as managed
data types like String. Similarly, it uses a native Win32 API call (GetUserName)
as well as a managed class (System::Console). The best part is that you do all this
in a single application (within a single function, in this case). Although you may
not have realized it, you’ve just written a mixed-mode application that mixes
native and managed code. Congratulations! You can do a lot with mixed-mode
coding, and you’ll see far more useful applications of that technique throughout
the later portions of this book.
You must have observed that I specified /clr as a compiler option. Let’s talk a
little more about that.
1.2.1 The /clr compiler option
To use the C++/CLI language features, you need to enable the /clr compiler
switch; without it, cl.exe behaves like a native C++ compiler. The /clr switch creates
a .NET application that’s capable of consuming .NET libraries and can take
advantage of CLR features such as managed types and garbage collection. You
can specify suboptions to the /clr option to further specify the type of assembly
you want created. Table 1.2 is a partial list of the /clr suboptions you can specify
and what they do. For a more complete list, refer to the MSDN documentation for
the C++ compiler command-line switches.
Now that we’ve discussed the command-line compiler options, let’s look at
how you can use the VC++ 2005 environment to create C++/CLI projects.
1.2.2 Using VC++ 2005 to create a /clr application
For any nontrivial program, it makes sense to use the Visual C++ development
environment, although it’s still good to know the compiler options available. I
believe that one of the biggest reasons for the popularity of the VC++ compiler is
the fact that it comes with a powerful development environment, and there’s no
reason we shouldn’t take advantage of it. For the rest of this chapter and the next
two chapters, we’ll use CLR-enabled console applications as we look at the C++/
CLI syntax and grammar. Those of you who want to follow along in your own console
project can type in the code as it’s written in the book.
NOTE: In later chapters, where the examples are longer and more complex, you
can use the book’s companion CD, which contains full source code for
the samples.
Creating a CLR console application with Visual C++ is straightforward. The steps
are as follows.
In the New Project Wizard dialog, choose CLR under Visual C++ in the
Project types tree control on the left, and select CLR Console Application from
the Templates list control on the right. You can use Figure 1.2 as a reference
when doing this.
Enter a name for the project, and click OK.
The wizard generates quite a few files for you. The one that should interest you
most is the CPP file that has the same name as the project. If you named your
project Chapter01Demo, you’ll see a Chapter01Demo.cpp file in your solution
that contains the wizard-generated main method. You must have used similar wizards
in the past when working on MFC, ATL, or Win32 API projects, so this should
be familiar.
You’ll notice something interesting about the way the generated main function
is prototyped:
int main(array<System::String ^> ^args)
This version of main is compatible with the entry-point prototypes available for
C# and VB.NET programs and adheres to the CLI definition of a managed entrypoint
function. The syntax may seem a little confusing right now (because we
haven’t yet begun exploring the C++/CLI syntax), but args is essentially a managed
array of System::String objects that represents the command-line arguments
passed to the application. Keep this important distinction in mind: Unlike
the native C++ main prototypes, the name of the program isn’t passed as the
zero-indexed argument. If you run the application without any command-line
arguments, the array will be empty. By default, the wizard sets the project to use
the /clr compilation option, but you can change that by using the Project Properties
dialog; you can access it by choosing Project > Properties or by using the
Alt-F7 keyboard shortcut.
NOTE: Your keyboard shortcuts will vary depending on your VS profile. This
book uses the shortcuts associated with the default VC++ profile.
Select General from Configuration Properties on the left, and you’ll see an option to
set the /clr compilation switch (you can choose from /clr, /clr:pure, /clr:safe,
and /clr:oldSyntax), as shown in Figure 1.3.
Now that you’ve seen how to create a C++/CLI project, let’s look at the typedeclaration
syntax for declaring CLI types (also referred to as CLR types). When
you learn how to declare and use CLR types, you get a proper feel for programming
on top of the CLR.
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.
C# developers may be a little confused by the usage of both class and 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 ref class 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 __value class.
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 ref class, for each, and value class are 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:
int ref = 0;
int value = ref;
bool each = 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 abstract and 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 abstract sealed classes 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:
ref class R2 abstract
{
public:
virtual void 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:
ref class R1
{
public:
virtual void 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:
ref class S sealed
{
};
ref class D : S // This won't compile
{ // Error C3246
};
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 of 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 Credit-
CardInfo 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.
Look at the following example of an abstract sealed class:
ref class SA abstract sealed
{
public:
static void DoStuff(){}
private:
static int bNumber = 0;
};
As mentioned earlier, abstract sealed classes 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 abstract sealed class would be
worthless, because you can never have an instance of such a class. An abstract
sealed class 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 abstract sealed class 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 (ref class/struct) only support public inheritance, and if you
skip the access keyword, public inheritance is assumed:
ref class Base
{
};
ref class 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:
interface class IBase
{
};
ref class Derived1 : private IBase {}; //error C3141
ref class Derived2 : protected IBase {}; //error C3141
ref class Derived3 : IBase {}; //public 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:
ref class RefBase {};
value class ValBase {};
interface class IBase {};
value class Derived1 : RefBase {}; //error C3830
value class Derived2 : ValBase {}; //error C3830
value class Derived3 : IBase {};
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 have been introduced
in VC++ 2005: the concept of handles.
WinConnections Conference Fall 2008 Don’t miss the premier event for Microsoft IT Professionals in Las Vegas, November 10-13. Register and book your room by August 25 and receive a FREE room night (based on a three night minimum stay).
Master SharePoint with 3 eLearning Seminars Learn how to build a better SharePoint infrastructure and enable powerful collaboration with MVPs Dan Holme and Michael Noel. Register today!
SharePointConnections Conference Fall 2008 Don’t miss the premier event for Microsoft IT Professionals in Las Vegas, November 10-13. Register and book your room by August 25 and receive a FREE room night (based on a three night minimum stay).
VMworld 2008 - Sign Up Today! Join your peers on September 15-18 at The Venetian Hotel in Las Vegas as VMware hosts VMworld 2008, the leading Virtualization event.
Microsoft® Tech•Ed EMEA 2008 IT Professionals Advance your thinking with new ideas and practical real-world solutions at Microsoft’s FIVE day technical infrastructure conference 3-7 Nov., 2008. Register before 26 September 2008 to save €300.
Order Your Fundamentals CD Today! Gain an introduction to Exchange, learn server security requirements, and understand how unified communications can play a role in your messaging strategies with this free Exchange CD.
Are You Really Compliant with Software Regulations? View this web seminar that will help you with compliance best practices and check out a management solution to assure that you won’t be in jeopardy of an audit.
Virtualization Congress Oct. 14-16 in London Don't miss Virtualization Congress, the premiere EMEA conference dedicated to hardware, OS and application virtualization. Oct. 14-16 in London.