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) :-
Collapse
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
Collapse
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
Collapse
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).
Collapse
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.
Collapse
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
Collapse
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.
Collapse
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
Collapse
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
Collapse
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.
Collapse
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.
Collapse
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.
Collapse
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
.
Collapse
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 :-
Collapse
#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 :-
Collapse
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 :-
Collapse
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.
Collapse
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 :-
Collapse
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
Collapse
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
Collapse
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.
출처 : http://www.codeproject.com/KB/mcpp/cppcliarrays.aspx