You are here:

C++/Arrays in MSVC++6

Advertisement


Question
QUESTION: Hi Ralph,

My MFC dialog box program has multiple edit boxes and I would like to make an array of the edit boxes. Since MSVC++ 6 doesn't allow arrays of edit boxes directly, can I make an array of pointers to the edit boxes?

I've tried:
CEdit* pArray[0] = (CEdit*)GetDlgItem(IDC_EDIT_0)
CEdit* pArray[1] = (CEdit*)GetDlgItem(IDC_EDIT_1)

I've also tried:
CEdit* array = new CEdit[2];
array[0].GetDlgItem(IDC_EDIT_0);
array[1].GetDlgItem(IDC_EDIT_1);

I've also tried to make an array through DoDataExchange that wouldn't work with MSVC++6 because it doesn't accept arrays there.

Though I can't seem to make any of these work, can you suggest something?

Thanks in advance for your help,
-Neil


ANSWER: There is nothing wrong with your basic idea - just the fact that this is _not_ the syntax to use.

In your first example pArray should have been defined first:

   CEdit * pArray[2];

Which would be a local array on the stack of a function call if it appears in a function definition or a global array of CEdit pointers if it appears outside. In either case you do not have to explicitly delete the pArray object as it will be deleted when it goes out of scope. Note: deleting the array deletes the pointers - which are built in types and have a no-operation destructor.

Or pArray could be defined like array in your second example:

   CEdit * pArray = new CEdit[2];

Which is created dynamically and therefore will require explicit deletion when you are done with pArray.

Now you just assign pointers to the array elements:

   pArray[0] = (CEdit*)GetDlgItem(IDC_EDIT_0);
   pArray[1] = (CEdit*)GetDlgItem(IDC_EDIT_1);
   
However, here you are using a C-style 'sledgehammer' cast. C++ has much better cast options that can prevent us doing silly things. In this case I would probably use a dynamic_cast which performs runtime checks to ensure the object we are casting is really of the type we are casting to. In this case it will check if the CWnd* returned by GetDlgItem (I presume this is the CWnd::GetDlgItem member function and not the raw Win32 API function) is really pointing to a CEdit object and can therefore be converted to a CEdit*. If this is not the case then a dynamic_cast will return a null pointer value (for reference types which do not have a null value it throws a std::bad_cast exception).

To use dynamic_cast we have to ensure RTTI (RunTime Type Information) is turned on. I forget if this is the default for VC++ 6 and what the options are - I suggest you poke around the project C++ settings in Visual Studio or check the help from the cl command line compiler driver.

We can use dynamic_cast like so:

   pArray[0] = dynamic_cast<CEdit*>( GetDlgItem(IDC_EDIT_0) );
   pArray[1] = dynamic_cast<CEdit*>( GetDlgItem(IDC_EDIT_1) );

And we can check the results like so:

   int i = 0;
   for (; i < 2; ++i )
   {
       if ( 0==pArray[i] )
       {
       // Could not get pointer for ith edit control.
       // Handle error: E.g. throw an exception:
         throw std::logic_error("Could not get pointer for dialog edit control" );
       }
   }

Of course what action you take if you fail to obtain a pointer for an edit control will depend on your specific requirements. Throwing an exception is quite a plausible action to take, although you may wish to be more informative in your error message. I used a logic_error as the casts should work if the code is correct and in sync with the resources for your dialog - that is all the control id macro names used do in fact refer to edit controls. However the code may fail if you update the dialog and do not change the supporting code - maybe someone changed IDC_EDIT_0 to a static label control and does not change the name for some obscure reason (or more likely due to laziness <g>) or IDC_EDIT_1 has its id name changed and so does not exist at all!

The next thing to do is to get rid of the magic number 2 that appears as the array size and as the termination value for the checking loop; The most obvious fix is to define a constant for this value:

   size_t const NumberOfEditControls(2);
   
   CEdit * pArray = new CEdit[NumberOfEditControls];

   // ...

   for (; i < NumberOfEditControls; ++i )
   {
       // ...
   }

However we could use a C++ library vector in place of the built-in array. In this case the number of edit controls is not a problem (so long as the number is within reason!) as std::vector type objects grow automatically when items are pushed_back to them and keep track of their size.

   #include <vector>

   // ...

   typedef std::vector<CEdit*>     CEditVector;

   // ...

   CEditVector editCtrlArray;
   
   editCtrlArray.push_back( dynamic_cast<CEdit*>(GetDlgItem(IDC_EDIT_0)) );
   editCtrlArray.push_back( dynamic_cast<CEdit*>(GetDlgItem(IDC_EDIT_1)) );
   
   CEditVector::iterator  i = editCtrlArray.begin();
   CEditVector::iterator  finish = editCtrlArray.end();
   
   for (; i != finish; ++i )
   {
       if ( 0==*i )
       {
       // Could not get pointer for ith edit control.
       // Handle error: E.g. throw an exception:
         throw std::logic_error("Could not get pointer for dialog edit control" );
       }
   }

In the above I define a type alias for vectors of CEdit pointers, CEditVector. I create an instance of a CEditVector and push back the pointers for the two edit controls. If more are added later then just add more calls to push_back on editCtrlArray.

I have chosen to use iterators to iterate over the elements of the vector. An iterator pointing to the initial element of editCtrlArray is returned by editCtrlArray.begin(). An iterator pointing to one past the end of the elements is returned by editCtrlArray.end(). We access an element through an iterator by de-referencing it like a pointer ( hence *i in 0==*i). Iterators can be incremented, hence the ++i in the for loop statement.

An alternative construct that is closer in style to the original for loop would be:

   int i = 0;
   for (; i < editCtrlArray.size(); ++i )
   {
       if ( 0==editCtrlArray[i] )
       {
       // Could not get pointer for ith edit control.
       // Handle error: E.g. throw an exception:
         throw std::logic_error("Could not get pointer for dialog edit control" );
       }
   }

Now I will end by pointing out some text from the MSDN description of CWnd::GetDlgItem:

   "The returned pointer may be temporary and should not be stored for later use."

So be warned. If you keep the pointers obtained from Wnd::GetDlgItem too long then they may well be invalid. I would think the time they are valid would be for the life time of an instance of your dialog. However note that dialogs may be destroyed and created when you are not expecting it - such as when changing tabs in a tabbed dialog display (I forget if this is actually such a case for sure, but vaguely remember that it is a likely candidate situation!).

Hope this helps you sort your code out and gives you some ideas. If you wish to know more of the C++ standard library then look at obtaining a decent reference on the subject such as "The C++ Standard Library a Tutorial and Reference" by Nicolai M. Josuttis. Note, however, that because of the age and lacking C++ features of Visual C++ 6, its standard library lacks one or two features. However most are there and I have used this implementation of the standard library with Visual C++ and MFC on a couple of real life commercial projects using the aforementioned tome as my main reference. Happy coding.

Oh, and the code snippets shown here have not been compiled so may contain mistakes and typos. If so then I apologise.



---------- FOLLOW-UP ----------

QUESTION: Hi Ralph,

I got in trouble right from the beginning...

In a test, I declared the pointer as you suggested:
CEdit * pArray = new CEdit[2];

Then I ran a test assigning pointers to the array elements:
pArray[0] = (CEdit*)GetDlgItem(IDC_EDIT_0);
pArray[1] = (CEdit*)GetDlgItem(IDC_EDIT_1);

to which I got an compiler error:
error 2582: CEdit : 'operator =' function is unavailable.

After re-reading everything about 'CEdit' that MSDN had to offer, all that I could add was: #include <afxwin.h> which was no help.

I got the same error when I tried:
pArray[0] = dynamic_cast<CEdit*>( GetDlgItem(IDC_EDIT_0) );
pArray[1] = dynamic_cast<CEdit*>( GetDlgItem(IDC_EDIT_1) );

-Neil

ANSWER: Sorry, my mistake. There was indeed an error in my code snippets, probably because I did not look carefully enough at the code I was referring to from your question. I apologise most profusely. The error is with the line:

   CEdit * pArray = new CEdit[2];

The above defines a pointer to CEdit objects _not_ a pointer to pointers to CEdit objects. This pointer is initialised with a dynamically created array of CEdit objects, whereas it should of course be initialised with a dynamically allocated array of pointers to CEdit objects. To correct these errors we will require an additional * in the type on the left hand side of the = _and_ a * after CEdit on the right hand side of the = :

   CEdit* * pArray = new CEdit*[2];

I have separated the *s on the left hand side above to help make it clear that new returns a pointer to pointers-to-CEdit objects (we read it backwards thus: pArray is a pointer to a pointer to CEdit objects). More usually we would write:

   CEdit ** pArray = new CEdit*[2];

or

   CEdit** pArray = new CEdit*[2];

or

   CEdit **pArray = new CEdit*[2];

Remember if you are using new [] you release the resources used by the dynamically assigned array using delete []:

   delete [] pArray;

Now the error you received is interesting. Often when this sort of mistake is made we get messages about different levels of indirection (a pointer can be thought of an indirect reference to an object), i.e. different levels of pointer-ness.

In this case however we are using C++ and class type objects were mistakenly involved (instead of pointers to them). In C++ we can overload operators for class type objects so the compiler went looking for an overloaded assignment operator for CEdit that took a CEdit* as its right hand side argument. On not finding one it produced an error to that effect.

I used the following code under MSVC++ 2005 (a.k.a MSVC++ 8.0) to check this explanation:

   class X
   {
   };
   
   int main()
   {
       X x0;
       X* pX0 = &x0;

       X* arrayXptr = new X[2];

       arrayXptr[0] = pX0;
       
       delete [] arrayXptr;
       return 0;
   }

Which has the same problem as the code you were attempting to compile on the line:

   X* arrayXptr = new X[2];

Attempting to compile with MSVC++ 2005 produced the following error:

   binary '=' : no operator found which takes a right-hand operand of type 'X *' (or there is no acceptable conversion)

Which is equivalent to the error you received, although possible a little more helpful.

Replacing:

   X* arrayXptr = new X[2];

with

   X* * arrayXptr = new X*[2];

Corrects the problem and the program compiles.

Oh and a better name for my CEditVector type alias in my original answer would have been something like CEditPointerVector:

   typedef std::vector<CEdit*>     CEditPointerVector;

Hope this fixes your problem and helps you to understand enough to spot similar problems in the future. Again, sorry for my mistake.



---------- FOLLOW-UP ----------

QUESTION: Hi Ralph,

Yes, your correction fixed the problem with the compiler, but now I have a new problem:
In the lines of code below, I've first placed the old line of code (before the new arrays were created). The second line of code utilizes the new arrays. The second line won't compile because the compiler expects an int, not a CEdit.

CEdit* pTrcLen = (CEdit*)GetDlgItem(IDC_EDIT_00);
CEdit* pTrcLen = (CEdit*)GetDlgItem(pArray[0]);

The next lines of code make the edit box visible, where the first is the original and the second utilizes the new array. Again the compiler expected an int rather than a CEdit in the second line:

GetDlgItem(IDC_EDIT_00)->ShowWindow(SW_SHOW);
GetDlgItem(pArray[0])->ShowWindow(SW_SHOW);

I experimented by changing 'CEdit' to 'int' throughout the test and changed the array initialization from:

CEdit** pArray = new CEdit*[2];

to:

int* pArray = new int[2];

but though this pleased the compiler, the program still erred (an unhandled exception...access violation) once it passed the line of code containing 'pArray'. There was no error at the initialization or the assignment of the pointer.

I'm sorry if I wasn't specific enough in my initial question.

Thanks in advance for helping me with my problems,
-Neil

Answer
OK, in the lines:

   CEdit* pTrcLen = (CEdit*)GetDlgItem(pArray[0]);
and
   GetDlgItem(pArray[0])->ShowWindow(SW_SHOW);

You are passing the value in pArray[0] to GetDlgItem - this, I presume from your previous questions, is already a CEdit* value returned by GetDlgItem. So why are you passing it to GetDlgItem _again_. I suspect this is a typo on your part and you meant to say something more like:

   CEdit* pTrcLen = pArray[0];
and
   pArray[0]->ShowWindow(SW_SHOW);

However it is your code so I cannot say for sure.

Please _read_ what you have written and check for such errors. I will not be pleased to continue to explain what appears to be silly typos on your part (but will of course help with silly mistakes on my part).

Of course maybe you really did not realise why you do not need to call GetDlgItem with results you previously obtained for it so here is a break down...

Yes changing to int might have worked fine for the compiler. Then however you would have to had filled the array with the resource id values (IDC_EDIT_00 etc) and _not_ pointers to the MFC control objects returned from GetDlgItem.

You _have_ to appreciate the difference between what is passed to GetDlgItem and what is returned from it.

If you store what you pass to GetDlgItem - i.e. resource id integer values - then you call GetDlgItem with these values to obtain a pointer to the required control each time:

   int* idArray = new int[2];
   
   idArray[0] = IDC_EDIT_00;
   idArray[1] = IDC_EDIT_01;
   
   // ...
   
  CEdit* pTrcLen = (CEdit*)GetDlgItem(idArray[0]);
  
  // ...
  
  GetDlgItem(idArray[0])->ShowWindow(SW_SHOW);

(or even pTrcLen->ShowWindow(SW_SHOW);

Alternatively, if you store pointers to the required controls as returned from GetDlgItem once per setup of the array elements then you use these values directly _without_ passing to GetDlgItem again:

   CEdit** pCEditArray = new CEdit*[2];

   pCEditArray[0] = (CEdit*)GetDlgItem(IDC_EDIT_00);
   pCEditArray[1] = (CEdit*)GetDlgItem(IDC_EDIT_01);

   // ...

   CEdit* pTrcLen = pCEditArray[0];

   // ...

   pCEditArray[0]->ShowWindow(SW_SHOW);

(or even pTrcLen->ShowWindow(SW_SHOW);

To help clarify what type of values are stored in the array I have changed the name to idArray for the example storing resource id values and pCEditArray for the example storing CEdit* values.

I will repeat the warning from my original answer here:

   "Now I will end by pointing out some text from the MSDN description of CWnd::GetDlgItem:

       'The returned pointer may be temporary and should not be stored for later use.'

   So be warned. If you keep the pointers obtained from CWnd::GetDlgItem too long then they may well be
   invalid. I would think the time they are valid would be for the life time of an instance of your dialog."

This is why I used the phrasing "once per setup of the array elements" for the storing CEdit* values example. If storing such control pointer values you will have to determine how frequently / infrequently you have to call CWnd::GetDlgItem and refresh the values in the array - or recreate the array totally - to ensure CEdit pointers are still valid. Isn't MS Windows programming fun?

Hope this clarifies your problems.

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.