| Keyword | Description |
|---|---|
| __abstract | Indicates that the class is abstract - that is, not all methods have an implementation, and to use the class, you must derive from it. |
| __box | Used to box a value type. Boxing creates an object with the value of the value type that has been boxed. |
| __delegate | Declares a delegate type. Delegates are essentially type-safe function pointers. |
| __event | Declares an event, a notification mechanism as part of a class. This keyword indicates that the class can generate the event, and it adds code to store the delegates invoked when the event raised. |
| __gc | Identifies that a class is managed by the .NET garbage collector or that a pointer points to a managed object. |
| __identifier | Used when the name of a type or a member is a keyword in C++ and indicates to the compiler to ignore C++ meaning of the word. |
| __interface | When combined with the __gc keyword, it allows you to declare a manage interface. |
| __nogc | Used to indicate that the type in mot managed by the .NET garbage collector or to indicate that a pointer points to a non-managed object. |
| __pin | Used on a pointer to pin the object it points to. This pinning means that for the scope of the pointer the object will not be moved in the managed heap during garbage collection. |
| __property | Indicates that a method is the get or set method for a property. |
| __sealed | Used on a class to indicate that the class is complete and cannot be extended through class derivation. |
| __try_cast | This cast operation performs a run-time type check and throws a managed exception if the call fails. |
| __typeof | Operator used to obtain the Type object for a particular type. |
| __value | Indicates that the type is lightweight and is created on the stack rather than on the managed heap. |
In addition, the compiler and linker have new switches for compiling .NET code. The most important new compiler switch is /clr. This switch tells the compiler to compile all code to Microsoft Intermediate Language (MSIL), regardless of whether the code is managed by the .NET garbage collector.
When you use the /clr switch, you also should have the following #using statement somewhere in the project:
#using <mscorlib.dll>The #using statement and the /clr switch go hand in hand - if you use one, you have to use the other.
Garbage collected reference types appears to solve the problem of leaking memory - your code merely has to allocate the object, and the garbage collector does the deallocation. However, garbage collection is more important than just this. In distributed applications memory allocation is extremely important because objects can be accessed across process or machine boundaries, which introduces the issue of which code has the responsibility to perform the cleanup.
If your application uses many small objects with short lifetimes, individually allocating these objects on the heap can be a significant performance hit. For this reason .NET provides value types. Because value types are normally created on the stack, their lifetime is short.
__gc class Robot {
private:
int ID;
...
public:
Robot() : ID(curID++) {};
~Robot() {};
};
Please note that instances of this class can be created only on the managed heap, as shown in the following code:
Robot *rbt = __gc new Robot();Although, we explicitly used the __gc modifier on the new operator to indicate that the managed operator is used, you don't have to use this syntax. Even if you omit this modifier, the compiler will still use managed operator new because the class is being created as managed. In other words, the following line produces exactly the same result:
Robot *rbt = new Robot();
Instances of a __gc class cannot be created on the stack. If we attempt create a stack-based instance
Robot rbt;the compiler will issue the following error:
error C3149: 'Robot' : illegal use of managed type 'Robot'; did you forget a '*'?
If you omit the __gc modifier or if you use the __nogc modifier in a class declaration, a native C++ class will be created. You can use pointers to native C++ objects in __gc classes, but you cannot use pointers to __gc objects in native C++ classes. All __gc classes look similar to native C++ classes, but they are subject to the .NET rules of reference types. Some of these rules are similar to C++; others apply more restrictions. The most significant restriction is that .NET allows only single-implementation inheritance, which means you cannot derive a class from more than one other class. Please find the example showing how to declare a managed class on the example page.
First of all, you are not allowed to use C++ const or volatile modifiers for __gc type methods. If a method marked as const
__gc class Robot{
...
public: void Print() const { ... };
...
};
then the compiler will generate the error C3842:
error C3842: 'Print': 'const' and 'volatile' qualifiers on member functions of managed types are not supportedHowever, you still can mark parameters passed by reference and a value of a method returned by reference as const.
Second, you cannot use default values for methods' parameters. For example, the following code
public: void GetHit(int damage=5) { ... };
will generate the error C3222:
error C3222: 'int damage' : cannot declare default arguments for member functions of a managed type
Managed types are normally passed as arguments of methods either by reference or by pointers. The following two examples show how this can be done:
__gc class Cannon {
public:
void Shoot(Robot &rbt)
{
rbt.GetHit(10);
};
};
...
Robot *robot = new Robot();
Cannon *cannon = new Cannon();
cannon->Shoot(*robot);
|
__gc class Cannon {
public:
void Shoot(Robot *rbt)
{
rbt->GetHit(10);
};
};
...
Robot *robot = new Robot();
Cannon *cannon = new Cannon();
cannon->Shoot(robot);
|
Managed types (both __gc and __values) can implement .NET properties. Strictly speaking, a property is not really a member of a type. It's a description that identifies methods on the type that can be called through property access. Properties are implemented with get_ and set_ methods. The get_ method is used to return the property, so its return type should be the same as the property. The set_ method is used to initialize the property, do typically, the method should not have a return type and its last parameter should be the same type as the property. To tell the compiler to generate a property, you use the __property modifier on the property methods:
__gc class Robot {
...
public:
__property int get_Energy() { return HP; };
__property void set_Energy(int hp)
{
HP = (0<=hp && hp<=MaxHP) ? hp : HP;
};
...
};
In this example we defined property Energy that lets users access private data member HP.
Now we can use this property for reading and writing:
while( robot->Energy > 0 ){
robot->Energy = robot->Energy - 5;
}
In fact, you can even use increment/decrement operators and various assignment operators. For example, the following code
is valid:
robot->Energy += 10; robot->Energy--;You don't have to provide both get_ and set_ methods. If you decide to have only get_ method for a property, then the property becomes read only. Similarly, if you only have the set_ method for a property, this property is write only. The only restriction you need to remember about properties is that you cannot have a property with the same name as a data member of the class. For more details see [1].
__value class Location {
private:
float x;
float y;
public:
Location() : x(0), y(0) {};
Location(float X, float Y) : x(X), y(Y) {};
...
};
Objects of a value type can be created either on the stack or on the non-managed heap.
You cannot create a value type directly on the managed heap. In other words, you can use declaration like
Location loc(100, 200);but you cannot use declaration like
Location *loc = new Location(100, 200);Code like this will generate the compiler's error C2716:
error C2716: 'operator new' allocates value types on the C++ heap; use '__nogc new Location'
All value types are automatically sealed. That is, you cannot derive from a value class. You don't need to use the __sealed modifier. A value type cannot derive from a reference type. Value types are typically used as records of data (the same way we use struct in C/C++). By default, the items are sequential - that is, in memory the fields appear in the order that they declared.
Enumerations are value types and have similar characteristics (allocated on the stack, implicitly sealed). However,
enumerations do have some some differences. For example, we are able to convert enumerated values to other types, can get
the names and values of members, and can create an enumerated value from a string
__value enum Direction : unsigned short int {
East = 0,
NorthEast = 45,
North = 90,
NorthWest = 135,
West = 180,
SouthWest = 225,
South = 270,
SouthEast = 315
};
The second difference is that the managed operator
Type *type = __typeof( Robot ); // CORRECT
but we cannot run code like
Robot *robot = new Robot();
Type *type = __typeof( *robot ); // WRONG
So, how do we find out the type of an object? For example, we have an array of objects of the BattleObject
class, but we know that these objects can be either of the type Robot, or Bomb, or Cannon
since they all derived from the class BattleObject. For this purpose we would suggest deriving your base class
from the managed class Object. The class Object provides public method
__gc class BattleObject : public Object {
...
};
__gc class Robot : public BattleObject {
...
};
__gc class Bomb : public BattleObject {
...
};
__gc class Cannon : public BattleObject {
...
};
then we can use the method GetType() inherited from the indirect base class Object:
Robot *robot = new Robot(); Type *type = robot->GetType();For example, we can print the hierarchy of the class:
Type *type = robot->GetType();
while( type ){
Console::Write(S" {0} -", type->Name);
type = type->BaseType;
}
To be able to compare two Type objects, class Type has method
BattleObject *list[];
...
Type *robot_type = __typeof( Robot );
for(i=0;i<list->Length;i++)
if( robot_type->Equals( list[i]->GetType() ) )
dynamic_cast<Robot*>(list[i])->Move();
Please find the complete example on the example page.
Robot* robots[] = __gc new Robot*[14]; for(int i=0;i<14;i++) robots[i] = new Robot();In fact, all managed arrays are actually objects derived from the Array object declared in the System name space. We can see the inheritance using the method
Console::WriteLine(S"'robots' type:");
Type *t = robots->GetType();
while( t ){
Console::WriteLine(S" {0}", t->ToString());
t = t->BaseType;
}
For our managed array robots the output will be:
'robots' type: Robot[] System.Array System.ObjectPlease find the complete example on the example page.
You can find the complete documentation about the Array class in the MSDN library. Here we would like to shortly mention only several properties and methods:
| Property | Description |
|---|---|
| IsFixedSize | Gets a boolean value indicating whether the Array has a fixed size. |
| Length | Gets a 32-bit integer that represents the total number of elements in all the dimensions of the Array. |
| Rank | Gets the rank (number of dimensions) of the Array. |
| Method | Description |
| Returns an IEnumerator for the Array. | |
| Gets a 32-bit integer that represents the number of elements in the specified dimension of the Array. | |
| Gets the lower bound of the specified dimension in the Array. | |
| Gets the Type of the current instance. | |
| Gets the upper bound of the specified dimension in the Array. | |
| Gets the value of the specified element in the current Array. The indexes are specified as an array of 32-bit integers. | |
| Sets the specified element in the current Array to the specified value. | |
| Searches a one-dimensional sorted Array for a value, using a binary search algorithm. | |
| Sets a range of elements in the Array to zero, to false, or to a null reference (Nothing in Visual Basic), depending on the element type. | |
| Returns the index of the first occurrence of a value in a one-dimensional Array or in a portion of the Array. | |
| Returns the index of the last occurrence of a value in a one-dimensional Array or in a portion of the Array. | |
| Reverses the order of the elements in a one-dimensional Array or in a portion of the Array. | |
| Sorts the elements in one-dimensional Array objects. |
The syntax to declare and access multidimensional array is quite different to native C++.
RobotQueue* cells[,] = __gc new RobotQueue*[M, N];This code creates two dimensional array of pointers to RobotQueue objects. This array contains M rows and N columns. Thus, the properties of this array are:
cells->Length = M * N - the total number of elements in the array cells->Rank = 2 - the number of dimensions cells->GetUpperBound(0) = M - the number of rows cells->GetUpperBound(1) = N - the number of columnsPlease find the complete example on the example page.
We can sort one dimensional arrays using static methods
__gc struct RobotComparer : Collections::IComparer {
int Compare(Object *a, Object *b)
{
Robot *left = dynamic_cast<Robot*>(a);
Robot *right = dynamic_cast<Robot*>(b);
return left->getHP() - right->getHP();
}
};
...
Array::Sort( robots, new RobotComparer );
Please find the complete example on the example page.
To declare a string and initialize its value, we usually use code like:
String *str = S"Hello, world!";The S prefix indicates that the string is a managed string. The String class has constructors that take an unmanaged pointer to a char buffer and an unmanaged pointer to a wide char buffer, which will convert the strings into the managed string. However, to do so requires the compiler to generate extra code, so if possible, you should always use managed string literals.
If we have two different Strings a and b we cannot compare them just using the equality operator ==:
String *a = S"Hello"; String *b = S"hello"; if( a == b ) ... // WRONGInstead, we need to use method Equals():
if( a->Equals(b) ) ... // CORRECTor
if( String::Equals(a, b) ) ... // CORRECT
Please consult the MSDN library to see all members of the String class.
__value class Complex {
private:
double x, y;
public:
Complex(double X, double Y) : x(X), y(Y) {};
static Complex& op_Addition(Complex& l, Complex& r)
{
return Complex(l.x+r.x, l.y+r.y);
};
static Complex& op_Subtraction(Complex& l, Complex& r)
{
return Complex(l.x-r.x, l.y-r.y);
};
static Complex& op_UnaryNegation(Complex& r)
{
return Complex(-r.x, -r.y);
};
static Complex& op_Multiply(Complex& l, double r)
{
return Complex(r*l.x, r*l.y);
};
static Complex& op_Multiply(double r, Complex& l)
{
return Complex(r*l.x, r*l.y);
};
};
Note that when we use value type, the operators will work exactly as we expect. However, if we use reference type, then
you need to be careful because the following code is not correct:
__gc calss Complex {
...
};
Complex *a = new Complex(1, 3);
Complex *b = new Complex(2, 1);
Complex *c;
c = a + b;
This code will perform pointer arithmetic because all variables are pointers. The solution for this problem is
to use references:
Complex &a = * new Complex(1, 3); Complex &b = * new Complex(2, 1); Complex &c = * new Complex(0, 0); c = a + b;but don't forget to overload the assignment operator:
__gc class Complex {
private:
double x, y;
public:
Complex(double X, double Y) : x(X), y(Y) {};
static Complex& op_Addition(Complex& l, Complex& r)
{
Complex *res = new Complex(l.x+r.x, l.y+r.y);
return *res;
};
static Complex& op_Subtraction(Complex& l, Complex& r)
{
Complex *res = new Complex(l.x-r.x, l.y-r.y);
return *res;
};
static Complex& op_UnaryNegation(Complex& r)
{
Complex *res = new Complex(-r.x, -r.y);
return *res;
};
static Complex& op_Multiply(Complex& l, double r)
{
Complex *res = new Complex(r*l.x, r*l.y);
return *res;
};
static Complex& op_Multiply(double r, Complex& l)
{
Complex *res = new Complex(r*l.x, r*l.y);
return *res;
};
static Complex& op_Assign(Complex &l, Complex &r)
{
l.x = r.x;
l.y = r.y;
return l;
};
};
| C++ operator symbol |
Name of alternative method |
Name of operator | C++ operator symbol |
Name of alternative method |
Name of operator |
|---|---|---|---|---|---|
| Not defined | ToXxx or FromXxx | op_Implicit | Not defined | ToXxx or FromXxx | op_Explicit |
| + (binary) | Add | op_Addition | - (binary) | Subtract | op_Subtraction |
| * (binary) | Multiply | op_Multiply | / | Divide | op_Division |
| % | Mod | op_Modulus | ^ | Xor | op_ExclusiveOr |
| & (binary) | BitwiseAnd | op_BitwiseAnd | | | BitwiseOr | op_BitwiseOr |
| && | And | op_LogicalAnd | || | Or | op_LogicalOr |
| = | Assign | op_Assign | << | LeftShift | op_LeftShift |
| >> | RightShift | op_RightShift | Not defined | LeftShift | op_SignedLeftShift |
| Not defined | RightShift | op_UnsignedRightShift | == | Equals | op_Equality |
| > | Compare | op_GreaterThan | < | Compare | op_LessThan |
| != | Compare | op_Inequality | >= | Compare | op_GreaterThanOrEqual |
| <= | Compare | op_LessThanOrEqual | *= | Multiply | op_MultiplicationAssignment |
| -= | Subtract | op_SubtractionAssignment | ^= | Xor | op_ExclusiveOrAssignment |
| <<= | LeftShift | op_LeftShiftAssignment | %= | Mod | op_ModulusAssignment |
| += | Add | op_AdditionAssignment | &= | BitwiseAnd | op_BitwiseAndAssignment |
| |= | BitwiseOr | op_BitwiseOrAssignment | , | None assigned | op_Comma |
| /= | Divide | op_DivisionAssignment | -- | Decrement | op_Decrement |
| ++ | Increment | op_Increment | - (unary) | Negate | op_UnaryNegation |
| + (unary) | Plus | op_UnaryPlus | ~ | OnesComplement | op_OnesComplement |