Managed classes are a powerful and versatile feature of Managed Extensions for C++. In addition to the standard features of a C++ class, the NGWS runtime and the Visual C++ compiler provide new features for managed classes:
A key concept for implementing modern component software, Managed Extensions provide built-in support for metadata and enables declaring new types and categories of metadata.
Managed Extensions allow the developer to specify that certain classes be allocated exclusively on the garbage-collected NGWS runtime heap, thereby removing the burden of lifetime management for the developer. Specifically, this means that you no longer must deal with complex bugs associated with reference counting or circular-reference leaks.
Any managed class written in Managed Extensions is immediately usable from Visual Basic or any other language that targets the NGWS runtime. This ability removes the need for error-prone MIDL files or the restriction of using only BSTRs for your string objects.
A new version of a managed class that includes new methods and data members can be deployed, while maintaining binary compatibility with existing client software.
Any file type that is able to contain metadata (.exe, .obj, or .dll) can be directly included into an Managed Extensions source file. The precompiled form also increases compilation speed.
Note An ordinary C++ class is referred to as an unmanaged class. Unmanaged classes are always allocated from the standard C++ runtime heap.
There are three kinds of managed classes:
Managed class type | Purpose |
---|---|
Garbage-Collected Classes | Allocated on the NGWS runtime heap and the most general-purpose type of managed class. |
Value Classes | Represents a small, short-lived data item (allocated on the stack) for which full garbage collection would be too costly. |
Managed Interface Classes | Offers direct support for COM-style interface programming in C++. |
The following features apply to all types of managed classes:
The following restrictions apply to all types of managed classes:
Because garbage-collected classes are allocated from the NGWS runtime heap, the following usage restrictions exist:
As in standard C++, a managed class may have a destructor, which can be called explicitly.
Note During garbage collection, the destructor is invoked before the associated memory is released.
A managed class may be explicitly destructed, either by calling the destructor directly or by using the delete operator. This allows an object to release its resources at a well-defined point. The destructor will not be called again when the garbage collector runs; only the memory will be reclaimed.
The following example demonstrates the explicit invoking of a managed object destructor:
void main () { ManagedObject* mObj1 = new ManagedObject(); mObj1->AllocateResources(); // Allocate resources from the managed heap. delete mObj1; // Frees those resources now. // Alternatively, the objects destructor may be called directly mObj1 = new ManagedObject(); mObj1->~ManagedObject(); }
If a garbage-collected class derives from a garbage-collected class authored in another language, the finalization method (if any) of the base class is treated as the destructor and is called automatically at the end of the destructor for the derived class.
Note A garbage-collected class may implement a finalization method. This method is called before unloading the object and is automatically called only once for each class.
If you are not certain that an object is dead, you can assign the value 0 to the pointer that references it. If that is the last pointer to the object, the garbage collector will automatically reclaim it.
A value class differs from a garbage-collected class in that instances can be allocated on the run-time stack and as static or heap-allocated variables under certain conditions. A value class is declared by applying the __value keyword to a class or struct declaration. Value classes are designed to hold small data items with short lifetimes, so as not to have the overhead of garbage collection for every allocation. They can also be members of a garbage-collected class.
In addition to those features common to all managed classes, the following features are also supported for value classes:
In addition to those restrictions common to all managed classes, the following restrictions are also in effect for value classes:
When the __box keyword is applied to a class or struct object, it is referred to as boxing the class or struct. This instance can then be treated as a managed object.
For every definition of a value class, there exists a unique boxed value class type corresponding to the original value class. All boxed value class types inherit from the garbage-collected class System::ValueType. Enumerated types that are boxed (sometimes referred to as managed enums) inherit from the garbage-collected class System::Enum. You cannot define boxed types directly; the compiler generates them on demand as needed. Referring to the boxed value class by its boxed type is more precise than using the generic type Object and allows the user to avoid expensive dynamic cast operations when accessing the underlying value type.
Because a boxed value class implicitly inherits from System::ValueType and therefore System::Object, a value class may directly call any function it specifically implements without being boxed first. This includes any overrides of virtual member functions defined in System::ValueType. The following example overrides the ToString member of ValueType and then invokes it directly from an instance of the value class V
:
__value struct V { // Override ValueType::ToString String *ToString() { return i.ToString(); } int i; }; void main() { V v = {10}; Console::WriteLine(v.ToString()); // Boxing not required }
A value class member can be accessed from the boxed version using the same syntax as accessing an unboxed value member. The following example demonstrates this by accessing the data member (i)
of a boxed value class (V)
:
__value struct V { int i; }; void main() { V v = {10}; __box V* pbV= __box(v); assert(pbV->i == 10); }
To call a virtual function of System::ValueType that has not been overridden in the value type, boxing is required. The following example uses the same value class (without the explicit override of ToString), invoking ToString by boxing the value class first:
__value struct V { int i; }; void main() { V v = {10}; v.i = 10; Console::WriteLine(v.ToString()); // Error: boxing required Console::WriteLine(__box(v)->ToString()); // OK: prints V }
An interface declaration is made by applying the __interface keyword to an existing class declaration:
__interface IMyInterface { void method_one(); };
A managed interface embodies the COM notion of an interface (with the added benefits of being managed) and is declared by applying the __gc keyword to an existing interface declaration:
__gc __interface IMyInterface { void method_one(); };
The previous code sample declares a managed interface (IMyInterface
) that is essentially a managed C++ abstract base class. Like an abstract base class, the methods of a managed interface are implicitly pure virtual methods.
The following rules apply to managed interfaces:
Managed Extensions for C++ allow two or more base interfaces of a class to declare identical methods. These ambiguous interface methods are implemented by providing a fully qualified name in the definition. The following example demonstrates this by declaring and defining the Calculate
method for both sample interfaces IAccount
and IStatement
:
__gc __interface IAccount { void Calculate(); }; __gc __interface IStatement { void Calculate(); }; __gc struct CMyStatement: IAccount, IStatement { void IAccount:: Calculate () {}; // OK void IStatement:: Calculate () {}; // OK };
Managed Extensions for C++ automatically provide a default implementation for methods that are not defined by the child class. Default implementation can also used to implement ambiguous base interface methods.
For example, the following code sample declares a class (CMyBase
) and a managed interface (IMyInterface
). Another class (CMyChild
) is derived from CMyBase
and IMyInterface
but does not provide an implementation for the derived method Calculate
:
__gc __interface IMyInterface { void Calculate(); }; __gc struct CMyBase { void Calculate(){} }; __gc struct CMyChild : CMyBase, IMyInterface { // By default, CMyChild uses CMyBase::Calculate to implement // IMyInterface::Calculate }; void main() { IMyInterface* pI = new CMyChild; // OK: CMyChild is not abstract pI->Calculate(); // ok: calls CMyChild::Calculate (via default //implementation) }