Value types have changed in various ways from Managed Extensions for
C++ to Visual C++ 2008.
A D V E R T I S E M E N T
In this section, we look at the CLR enum
type and the value class type, together with a look at boxing and
access to the boxed instance on the CLR heap, as well as a look at
interior and pinning pointers. There have been extensive language
changes in this area.
In
This Section
Value Types and Their Behaviors
Discusses changes in the declaration and behavior of
enums.
Implicit Boxing of Value Types
Discusses the motivation for implicit boxing of
value types and the consequent changes in behavior.
A Tracking Handle to a Boxed Value
Discusses how implicit boxing of value types
translates to a tracking handle to the boxed value
object.
class="normaltext"
Discusses changes to class="normaltext", including
inherited virtual methods, class default constructors,
interior pointers, and pinning pointers.
CLR Enum Type
The declaration and behavior of enums has changed from Managed
Extensions for C++ to Visual C++ 2008.
The Managed Extensions enum
declaration is preceded by the __value
keyword. The idea here is to distinguish the native enum from the
CLR enum which is derived from System::ValueType,
while suggesting an analogous functionality. For example:
Copy Code
__value enum e1 { fail, pass };
public __value enum e2 : unsigned short {
not_ok = 1024,
maybe, ok = 2048
};
The new syntax solves the problem of distinguishing native and
CLR enums by emphasizing the class nature of the latter rather than
its value type roots. As such, the __value
keyword is discarded, replaced with the spaced keyword pair of
enum class. This provides a paired
keyword symmetry to the declarations of the reference, value, and
interface classes:
Copy Code
enum class ec;
value class vc;
ref class rc;
interface class ic;
The translation of the enumeration pair e1
and e2 in the new syntax looks as follows:
Copy Code
enum class e1 { fail, pass };
public enum class e2 : unsigned short {
not_ok = 1024,
maybe, ok = 2048
};
Apart from this small syntactic change, the behavior of the CLR
enum type has been changed in a number of ways:
A forward declaration of a CLR enum is no longer supported.
There is no mapping. It is simply flagged as a compile-time
error.
Copy Code
__value enum status; // Managed Extensions: ok
enum class status; // new syntax: error
The overload resolution between the built-in arithmetic
types and the Object class hierarchy
has reversed between the two language versions! As a
side-effect, CLR enums are no longer implicitly converted to
arithmetic types.
In the new syntax, a CLR enum maintains its own scope, which
is not the case in Managed Extensions. Previously, the
enumerators were visible within the containing scope of the enum.
Now, the enumerators are encapsulated within the scope of the
enum.
CLR
Enums are a Kind of Object
Consider the following code fragment:
Copy Code
__value enum status { fail, pass };
void f( Object* ){ Console::WriteLine("f(Object)\n"); }
void f( int ){ Console::WriteLine("f(int)\n"); }
int main()
{
status rslt = fail;
f( rslt ); // which f is invoked?
}
For the native C++ programmer, the natural answer to the
question of which instance of the overloaded
f is invoked is that of
f(int). An enum is a symbolic
integral constant, and it participates in the standard
integral promotions which take precedence in this case. And
in fact in Managed Extensions this was the instance to which
the call resolves. This caused a number of surprises � not
when we used them in a native C++ frame of mind � but when
we needed them to interact with the existing BCL (Base Class
Library) framework, where an Enum
is a class indirectly derived from Object.
In the Visual C++ 2008 language design, the instance of
f invoked is that of
f(Object^).
The way Visual C++ 2008 has chosen to enforce this is to
not support implicit conversions between a CLR enum type and
the arithmetic types. This means that any assignment of an
object of a CLR enum type to an arithmetic type will require
an explicit cast. So, for example, given
Copy Code
void f( int );
as a non-overloaded method, in Managed Extensions, the
call
Copy Code
f( rslt ); // ok: Managed Extensions; error: new syntax
is ok, and the value contained within
rslt is implicitly converted into an integer value.
In Visual C++ 2008, this call fails to compile. To correctly
translate it, we must insert a conversion operator:
Copy Code
f( safe_cast( rslt )); // ok: new syntax
The
Scope of the CLR Enum Type
One of the changes between the C and C++ languages was
the addition in C++ of scope within the struct facility. In
C, a struct is just a data aggregate without support of
either an interface or an associated scope. This was quite a
radical change at the time and was a contentious issue for
many new C++ users coming from the C language. The
relationship between the native and CLR enum is analogous.
In Managed Extensions, an attempt was made to define
weakly injected names for the enumerators of a CLR enum in
order to simulate the absence of scope within the native
enum. This did not prove successful. The problem is that
this causes the enumerators to spill into the global
namespace, resulting in difficult to manage name-collisions.
In the new syntax, we have conformed to the other CLR
languages in supporting scopes within the CLR enum.
This means that any unqualified use of an enumerator of a
CLR enum will not be recognized by the new syntax. Let's
look at a real-world example.
Each of the three unqualified uses of the enumerator
names ((1),
(2), and
(3)) will need to be
qualified in the translation to the new syntax in order for
the source code to compile. Here is a correct translation of
the original source code:
This changes the design strategy between a native and a
CLR enum. With a CLR enum maintaining an associated scope in
Visual C++ 2008, it is neither necessary nor effective to
encapsulate the declaration of the enum within a class. This
idiom evolved around the time of cfront 2.0 within Bell
Laboratories also in order to solve the global name
pollution problem.
In the original beta release of the new iostream library
by Jerry Schwarz at Bell Laboratories, Jerry did not
encapsulate all the associated enums defined for the
library, and the common enumerators such as
read, write,
append, and so on, made it nearly
impossible for users to compile their existing code. One
solution would have been to mangle the names, such as
io_read,
io_write, etc. A second solution would have been to
modify the language by adding scope to an enum, but this was
not practicable at the time. The middle solution was to
encapsulate the enum within the class, or class hierarchy,
where both the tag name and enumerators of the enum populate
the enclosing class scope.) That is, the motivation for
placing enums within classes, at least originally, was not
philosophical, but a practical response to the global
name-space pollution problem.
With the Visual C++ 2008 enum, there is no longer any
compelling benefit to encapsulating an enum within a class.
In fact, if you look at the System
namespaces, you will see that enums, classes, and interfaces
all inhabit the same declaration space.
Implicit Boxing of Value Types
The boxing of value types has changed from Managed Extensions for
C++ to Visual C++ 2008.
In language design, we imposed a
philosophical position in lieu of practical experience with the
feature and, in practice, it was a mistake. As an analogy, in the
original multiple inheritance language design, Stroustrup decided
that a virtual base class sub-object could not be initialized within
a derived class constructor, and therefore the language required
that any class serving as a virtual base class must define a default
constructor. It is that default constructor that would be invoked by
any subsequent virtual derivation.
The problem of a virtual base class hierarchy is that
responsibility for the initialization of the shared virtual
sub-object shifts with each subsequent derivation. For example, if I
define a base class for which initialization requires the allocation
of a buffer, the user-specified size of that buffer might be passed
as an argument to the constructor. If I then provide two subsequent
virtual derivations, call them inputb and
outputb, each provides a particular value
to the base class constructor. Now, when I derived an
in_out class from both
inputb and outputb, neither of
those values to the shared virtual base class sub-object can
sensibly be allowed to evaluate.
Therefore, in the original language design, Stroustrup disallowed
the explicit initialization of a virtual base class within the
member initialization list of the derived class constructor. While
this solved the problem, in practice the inability to direct the
initialization of the virtual base class proved impracticable. Keith
Gorlen of the National Institute of Health, who had implemented a
freeware version of the SmallTalk collection library called nihcl,
was a principle voice in convincing Stroustrup that he had to come
up with a more flexible language design.
A principle of Object-Oriented hierarchical design holds that a
derived class should concern itself only with the non-private
implementation of its immediate base classes. In order to support a
flexible initialization design for virtual inheritance, Stroustrup
had to violate this principle. The most derived class in a hierarchy
assumes responsibility for all virtual sub-object initialization
regardless of how deep into the hierarchy it occurs. For example,
inputb and outputb
are both responsible for explicitly initializing their immediate
virtual base class. When in_out derives
from both inputb and
outputb, in_out becomes responsible
for the initialization of the once removed virtual base class, and
the initialization made explicit within inputb
and outputb is suppressed.
This provides the flexibility required by language developers,
but at the cost of a complicated semantics. This burden of
complication is stripped away if we restrict a virtual base class to
be without state and simply allow it to specify an interface. This
is a recommended design idiom within C++. Within CLR programming, it
is raised to policy with the Interface type.
Here is a simple code sample� and in this case, the explicit
boxing is unnecessary: