Introduction
Managed arrays are allocated on the CLI heap as opposed to native arrays
which are allocated on the unmanaged C++ heap, which essentially means that they
are typical garbage collected .NET objects. And throughout this article the
term array will refer to a managed array, and if at all a native array is
mentioned, it will explicitly be termed as a native array. Some of you
might have seen
my article on using managed arrays with the old MC++ syntax and I am sure a
few of you would have let out pained groans at the really peculiar syntax. Well,
those people should be delighted to know that C++/CLI supports managed arrays
using a far more intuitive syntax than which existed in the older syntax.
Basic features
Here are some of the basic features of managed arrays in C++/CLI :-
- Syntax resembles that of C++ templates
System::Array
is automatic base type for all managed arrays
- Allocated on the CLR heap (means they get Garbage Collected)
- Rank of the array need not be 1 (arrays with rank 1 are called single
dimensional and those with rank >1 are called multi dimensional)
- Easily supports jagged arrays (unlike in the older syntax, jagged arrays
are easier to declare and use)
- Implicit conversion to and explicit conversion from
System::Array
BCL class
- The rank and dimensions of an array object cannot be changed, once an
array has been instantiated
Pseudo-template of an array type
The declaration and usage of array types in C++/CLI seems to use an imaginary
template type (there is no such template of course, it�s all VC++ compiler
magic) :-
namespace stdcli::language
{
template<typename T, int rank = 1>
ref class array : System::Array {};
}
array
is declared inside the stdcli::language
namespace so as to avoid conflicts with existing source code
Single dimensional array usage
Arrays of a reference type
ref class R
{
public:
void Test1(int x)
{
array<String^>^ strarray = gcnew array<String^>(x);
for(int i=0; i<x; i++)
strarray[i] = String::Concat("Number ",i.ToString());
for(int i=0; i<x; i++)
Console::WriteLine(strarray[i]);
}
};
The syntax does look different from that used for native arrays; C++ coders
who have used templates should find this a lot more intuitive than people who
come from a non-C++ background, but eventually they'll get comfortable with it.
Arrays of a value type
ref class R
{
public:
void Test2(int x)
{
array<int>^ strarray = gcnew array<int>(x);
for(int i=0; i<x; i++)
strarray[i] = i * 10;
for(int i=0; i<x; i++)
Console::WriteLine(strarray[i]);
}
};
Unlike in the old syntax, array syntax for value types is exactly the same as
that for managed types.
Multi dimensional array usage
Multi dimensional arrays are managed arrays that have a rank greater than 1.
They are not arrays of arrays (those are jagged arrays).
ref class R
{
public:
void Test3()
{
array<String^,2>^ names = gcnew array<String^,2>(4,2);
names[0,0] = "John";
names[1,0] = "Tim";
names[2,0] = "Nancy";
names[3,0] = "Anitha";
for(int i=0; i<4; i++)
if(i%2==0)
names[i,1] = "Brown";
else
names[i,1] = "Wilson";
for(int i=0; i<4; i++)
Console::WriteLine("{0} {1}",names[i,0],names[i,1]);
}
};
Jagged arrays
Jagged arrays are non-rectangular, and are actually arrays of arrays. The new
template-style array syntax simplifies the declaration and use of jagged arrays,
which is a major improvement over the old syntax where jagged arrays had to be
artificially simulated by the developer.
ref class R
{
public:
void Test()
{
array<array<int>^>^ arr = gcnew array<array<int>^> (5);
for(int i=0, j=10; i<5; i++, j+=10)
{
arr[i] = gcnew array<int> (j);
}
Console::WriteLine("Rank = {0}; Length = {1}",
arr->Rank,arr->Length);
for(int i=0; i<5; i++)
Console::WriteLine("Rank = {0}; Length = {1}",
arr[i]->Rank,arr[i]->Length);
}
};
Using a typedef
to simplify jagged array usage
typedef array<array<int>^> JaggedInt32Array;
typedef array<array<String^>^> JaggedStringArray;
ref class R
{
public:
void Test()
{
JaggedInt32Array^ intarr = gcnew JaggedInt32Array(10);
JaggedStringArray^ strarr = gcnew JaggedStringArray(15);
}
};
Directly initialize an array
The new syntax allows painless direct initialization of arrays.
ref class R1
{
public:
void Test()
{
array<String^>^ arr1 = gcnew array<String^> {"Nish", "Colin"};
array<String^>^ arr2 = {"Nish", "Smitha"};
array<Object^,2> ^ multiobarr = {{"Nish", 100}, {"Jambo", 200}};
}
};
Arrays as function arguments
ref class R
{
public:
void ShowStringArray(array<String^>^ strarr)
{
for(int i=0; i<strarr->Length; i++)
Console::WriteLine(strarr[i]);
}
void Show2DInt32Array(array<int,2>^ arr)
{
for(int i=0; i<arr->GetLength(0); i++)
{
Console::WriteLine("{0} times 2 is {1}",arr[i,0],arr[i,1]);
}
}
};
void _tmain()
{
R^ r = gcnew R();
r->ShowStringArray(gcnew array<String^> {"hello", "world"});
array<int,2>^ arr = { {1,2}, {2,4}, {3,6}, {4,8} };
r->Show2DInt32Array(arr);
}
Returning arrays from functions
ref class R
{
public:
array<String^>^ GetNames(int count)
{
array<String^>^ arr = gcnew array<String^>(count);
for(int i=0; i<count; i++)
{
arr[i] = String::Concat("Mr. ",(i+1).ToString());
}
return arr;
}
void ShowNames(array<String^>^ arr)
{
for(int i=0; i<arr->Length; i++)
Console::WriteLine(arr[i]);
}
};
void _tmain()
{
R^ r = gcnew R();
array<String^>^ arr = r->GetNames(7);
r->ShowNames(arr);
}
Array covariance
You can assign an array of type R to an array of type B, where B is a direct
or indirect parent of R, and R is a ref
class.
ref class R1
{
};
ref class R2 : R1
{
};
void _tmain()
{
array<R1^>^ arr1 = gcnew array<R1^>(4);
array<R2^>^ arr2 = gcnew array<R2^>(4);
array<R1^>^ arr3 = arr2;
array<R1^>^ arr4 = gcnew array<R2^>(4);
}
Parameter arrays
C++/CLI has support for parameter arrays. There can only be one such
parameter array per function and it also needs to be the last parameter.
ref class R
{
public:
void Test(String^ s, [ParamArray] array<int>^ arr )
{
Console::Write(s);
for(int i=0; i<arr->Length; i++)
Console::Write(" {0}",arr[i]);
Console::WriteLine();
}
};
void _tmain()
{
R^ r = gcnew R();
r->Test("Nish");
r->Test("Nish",1);
r->Test("Nish",1,15);
r->Test("Nish",1,25,100);
}
Right now the only supported syntax uses the ParamArray
attribute, but the eventual syntax will also support the simpler style shown below :-
void Test(String^ s, ... array<int>^ arr )
Calling System::Array
methods
Since every C++/CLI array is implicitly a System::Array
object,
we can use System::Array
methods on CLI arrays.
ref class R
{
public:
bool CheckName(array<String^>^ strarr, String^ str)
{
Array::Sort(strarr);
return Array::BinarySearch(strarr,str) < 0 ? false : true;
}
};
void _tmain()
{
R^ r = gcnew R();
array<String^>^ strarr = {"Nish","Smitha",
"Colin","Jambo","Kannan","David","Roger"};
Console::WriteLine("Nish is {0}",r->CheckName(strarr,"Nish") ?
gcnew String("Present") : gcnew String("Absent"));
Console::WriteLine("John is {0}",r->CheckName(strarr,"John") ?
gcnew String("Present") : gcnew String("Absent"));
}
I've used System::Sort
and System::BinarySearch
in
the above example.
Here's another snippet that clearly demonstrates the implicit conversion to
System::Array
.
ref class R
{
public:
void ShowRank(Array^ a)
{
Console::WriteLine("Rank is {0}",a->Rank);
}
};
void _tmain()
{
R^ r = gcnew R();
r->ShowRank( gcnew array<int>(10) );
r->ShowRank( gcnew array<String^,2>(10,2) );
r->ShowRank( gcnew array<double,3>(10,3,2) );
r->ShowRank( gcnew array<int> {1,2,3} );
r->ShowRank( gcnew array<int,2> {{1,2}, {2,3}, {3,4}} );
}
Arrays of non-CLI objects
You can declare C++/CLI arrays where the array type is of a non-CLI object.
The only inhibition is that the type needs to be a pointer type. Consider the
following native C++ class :-
#define Show(x) Console::WriteLine(x)
class N
{
public:
N()
{
Show("N::ctor");
}
~N()
{
Show("N::dtor");
}
};
Now here's how you can declare an array of this type :-
ref class R
{
public:
void Test()
{
array<N*>^ arr = gcnew array<N*>(3);
for(int i=0; i<arr->Length; i++)
arr[i] = new N();
}
};
Put this class to use with the following test code :-
void _tmain()
{
R^ r = gcnew R();
r->Test();
Show("Press any key...");
Console::ReadKey();
}
There, that worked. Of course the destructors for the array elements did not
get called, and in fact they won't get called even if a Garbage Collection takes
place and the array object is cleaned up. Since they are native objects on the
standard C++ heap, they need to have delete
called on them
individually.
ref class R
{
public:
void Test()
{
array<N*>^ arr = gcnew array<N*>(3);
for(int i=0; i<arr->Length; i++)
arr[i] = new N();
for(int i=0; i<arr->Length; i++)
delete arr[i];
}
Ok, that's much better now. Or if you want to avoid calling delete
on each
object, you can alternatively do something like this :-
ref class R
{
public:
void Test()
{
N narr[3];
array<N*>^ arr = gcnew array<N*>(3);
for(int i=0; i<arr->Length; i++)
arr[i] = &narr[i];
}
};
This yields similar results to the above snippet. Alternatively you could
init the array members in its containing class's constructor and delete them in
the destructor, and then use the containing class as an automatic variable (C++/CLI
supports deterministic destruction).
Direct manipulation of CLI arrays using native pointers
Here's some code that shows how you can directly manipulate the contents of an array using native pointers. The first sample is for a single dimensional array and the second is for a jagged array.
Natively accessing a single-dimensional array
void Test1()
{
array<int>^ arr = gcnew array<int>(3);
arr[0] = 100;
arr[1] = 200;
arr[2] = 300;
Console::WriteLine(arr[0]);
Console::WriteLine(arr[1]);
Console::WriteLine(arr[2]);
pin_ptr<int> p1 = &arr[0];
int* p2 = p1;
while(*p2)
{
(*p2)++;
p2++;
}
Console::WriteLine(arr[0]);
Console::WriteLine(arr[1]);
Console::WriteLine(arr[2]);
}
Natively accessing a jagged array
void Test2()
{
array<array<int>^>^ arr = gcnew array<array<int>^>(2);
arr[0] = gcnew array<int>(2);
arr[1] = gcnew array<int>(2);
arr[0][0] = 10;
arr[0][1] = 100;
arr[1][0] = 20;
arr[1][1] = 200;
Console::WriteLine(arr[0][0]);
Console::WriteLine(arr[0][1]);
Console::WriteLine(arr[1][0]);
Console::WriteLine(arr[1][1]);
pin_ptr<int> p1 = &arr[0][0];
pin_ptr<int> p2 = &arr[1][0];
int* p3[2];
p3[0] = p1;
p3[1] = p2;
Console::WriteLine(p3[0][0]);
Console::WriteLine(p3[0][1]);
Console::WriteLine(p3[1][0]);
Console::WriteLine(p3[1][1]);
int** p4 = p3;
for(int i=0; i<2; i++)
for(int j=0; j<2; j++)
p4[i][j] += 3;
Console::WriteLine(arr[0][0]);
Console::WriteLine(arr[0][1]);
Console::WriteLine(arr[1][0]);
Console::WriteLine(arr[1][1]);
}
Essentially we use a pinning pointer to the GC'd heap and then treat the casted native pointer as if it were pointing to a native array. Gives us a really fast method to manipulate array content!
Conclusion
Array syntax in C++/CLI is definitely an aesthetic improvement over the older
MC++ syntax, and it also brings in a consistency of syntax that was severely
lacking earlier. The template-style syntax should give a natural feel for C++
coders though it might take a little while before you fully get used to it. As
usual, I request you to freely post any comments, suggestions, criticism, praise
etc. that you might have for me.
History
- Jul 13, 2004 - First published
- Aug 12, 2004 - Updated article by adding sections on using non-CLI
objects as array members, and directly accessing an array using pointers