Click here to Skip to main content
Click here to Skip to main content

Arrays in C++/CLI

, 11 Aug 2004
Rate this:
Please Sign up or sign in to vote.
The article exposes the new array syntax available in C++/CLI for the declaration and use of CLI arrays

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);
    /*
      Output :-
        Rank = 1; Length = 5
    */

    for(int i=0; i<5; i++)
      Console::WriteLine("Rank = {0}; Length = {1}",
        arr[i]->Rank,arr[i]->Length);
    /*
      Output :-
        Rank = 1; Length = 10
        Rank = 1; Length = 20
        Rank = 1; Length = 30
        Rank = 1; Length = 40
        Rank = 1; Length = 50
    */
  }
};

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()
  {
    //Single dimensional arrays
    array<String^>^ arr1 = gcnew array<String^> {"Nish", "Colin"};
    array<String^>^ arr2 = {"Nish", "Smitha"};
    
    //Multi dimensional arrays
    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);
}

//Output :-

/*
  hello
  world
  1 times 2 is 2
  2 times 2 is 4
  3 times 2 is 6
  4 times 2 is 8
*/

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();
}

/* Output:

N::ctor
N::ctor
N::ctor
Press any key...

*/

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];
   }
   //...

/* Output

N::ctor
N::ctor
N::ctor
N::dtor
N::dtor
N::dtor
Press any key...

*/

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]);

    /*
    Output :-
      100
      200
      300
    */

    // Modifying the array using a native int* 
    // that points to a pinned pointer in GC'd heap
    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]);

    /*
    Output :-
      101
      201
      301
    */
} 

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]);

    /*
    Output:
      10
      100
      20
      200
    */


    // Copying the managed jagged array to
    // a native array of pointers and accessing
    // the members using the native array
    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]);

    /*
    Output:
      10
      100
      20
      200
    */

    // Assigning the native array of pointers
    // to a native int** and modifying the array
    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]);

    /*
    Output:
      13
      103
      23
      203
    */
}

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

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Nish Sivakumar

United States United States
Nish is a real nice guy who has been writing code since 1990 when he first got his hands on an 8088 with 640 KB RAM. Originally from sunny Trivandrum in India, he has been living in various places over the past few years and often thinks it’s time he settled down somewhere.
 
Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - www.voidnish.com where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI. Oh, and you might want to check out his blog on C++/CLI, MFC, .NET and a lot of other stuff - blog.voidnish.com.
 
Nish loves reading Science Fiction, P G Wodehouse and Agatha Christie, and also fancies himself to be a decent writer of sorts. He has authored a romantic comedy Summer Love and Some more Cricket as well as a programming book – Extending MFC applications with the .NET Framework.
 
Nish's latest book C++/CLI in Action published by Manning Publications is now available for purchase. You can read more about the book on his blog.
 
Despite his wife's attempts to get him into cooking, his best effort so far has been a badly done omelette. Some day, he hopes to be a good cook, and to cook a tasty dinner for his wife.

Comments and Discussions

 
QuestionArray of String returning as output parameter from C++ to C# Pinmembersvollala115-Jun-10 4:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 12 Aug 2004
Article Copyright 2004 by Nish Sivakumar
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid