C++/Uhhh...

Advertisement


Question
Hey Ralph,
 Here's my problem, I am writing a menu and one of the choices is to allow the user to input a list of people.  Instead of declaring a cstring for every name, is there a way I can ask the user to input the number of names, "x", and have the computer initilize "x" number of cstrings?

Answer
I am not sure what you mean by cstring - do you mean a C style zero terminated character array string or some class called cstring or maybe the Microsoft MFC/ATL libraries' CString class?

No matter what the exact type of the string objects are, the obvious answer is allocate the memory dynamically at runtime (note: often (but not always) static implies compile time and dynamic implies runtime).

I shall give a very quick 5 minute introduction to the subject as far as C++ is concerned.

In C++ the new and delete operators are used to create and destroy objects dynamically at runtime. These operators come in 2 forms: The plain scalar forms new and delete for use with single object allocation such as an int or single class instance. The second form is the array form new [] and delete [] for creating and destroying arrays of objects at runtime. Both forms of new return a pointer - either to the object or to the first object. Likewise both forms of delete take a pointer returned by the _matching_ form of new. The usual standard forms of new also throw a std::bad_alloc exception if they cannot obtain the resources (i.e. memory) to complete the request.

The rules are:

1/ if your program creates an object or objects at runtime then it is responsible for ensuring such objects are destroyed when no longer required so that any resources held by such objects are released (including, but not limited to, the memory they occupy).

2/ if you create with new then destroy with delete. If you create with new [] then destroy with delete [].

So in your case you could create an array of n cstrings (assuming cstring to be some class) at runtime like so:

   unsigned int n(0);
   std::cout << "Enter size of array: ";
   std::cin >> n;

   // Create an array of n cstrings
   cstring * n_cstrings = new cstring[n];

   // Do stuff with the array
   n_cstrings[0] = "string 0";
   n_cstrings[n-1] = "string n-1";

   // Done with array so delete using _correct_ array form of delete
   delete [] n_cstrings;

Note that the assignments only work if the cstring class has a relevant assignment operator (operator=).

Now things are a little more complex if by cstring you mean a C style zero terminated array of characters. This is because new can only allocate single dimensional arrays directly. This leaves you two options: simulate a 2 dimensional array of characters (an array of C strings in this context means an array of arrays of char) using a single dimension array or allocate an array of pointers to char then allocate each individual string separately. This of course also assumes we know the maximum length of the individual C strings in the array.

In the first case we allocate an array of char that can hold characters for _all_ strings in the array. Suppose each C string can hold a maximum of 255 characters plus the zero terminating character, making each string 256 characters in length. If the user requests 10 such strings then we need space for 10*256 characters, so we modify the previous example like so:

   unsigned int const MaxStringLength(255);
   unsigned int const StringSize(MaxStringLength + 1);
   unsigned int n(0);
   std::cout << "Enter size of array: ";
   std::cin >> n;

   // Create an array large enough for n C strings
   char * n_C_strings = new char[n * StringSize];

Now to access each string we work with pointers and perform some pointer arithmetic. We multiply the string index by the StringSize values (i.e. by 256 in this example) and add it to the start address of the array (note the arithmetic simplifications I use for indexes 0 and 1):

   // Do stuff with the array
   strcpy( n_C_strings, "string 0");
   strcpy( n_C_strings+StringSize, "string 1");
   strcpy( n_C_strings+((n-1)*StringSize), "string n-1");

   // Done with array so delete using _correct_ array form of delete
   delete [] n_C_strings;

Unsurprisingly as these are C strings we have to use the C string library functions declared in the C++ cstring header to operate on them (or perform the equivalent operations ourselves longhand). In this case we use the strcpy function to copy the contents of the ( C ) string literals to the required portion of the array.

The second option is to dynamically create an array of pointers to char and then allocate each string and assign its pointer to one of these pointers:

   unsigned int const MaxStringLength(255);
   unsigned int const StringSize(MaxStringLength + 1);
   unsigned int n(0);
   std::cout << "Enter size of array: ";
   std::cin >> n;

   // Create an array of n pointers to C strings
   // Note we allocate pointers to char (char*) and
   // get a pointer to the pointers to char (char**) in return
   char ** n_C_strings = new char*[n * StringSize];

   // Create arrays of char for each of the pointers allocated above:
   for ( int i(0); i != n; ++i )
   {
       n_C_strings[i] = new char[StringSize];
   }

This is more complicated than before but the access is easier:

   // Do stuff with the array
   strcpy( n_C_strings[0], "string 0");
   strcpy( n_C_strings[1], "string 1");
   strcpy( n_C_strings[2], "string n-1");

We still have to use strcpy etc. but at least we can refer to each string individually by using array subscript syntax.

However, as for the creation process the destruction process is more complex and has to be done in two stages like the creation but in reverse order:

   // Done with arrays so delete using _correct_ array forms of delete

   // First delete the individual C strings
   for ( int i(0); i != n; ++i )
   {
       delete [] n_C_strings[i];
   }

   // Finally delete the array of pointers to the strings.
   delete [] n_C_strings;

Now all these new and delete operations are obviously fragile - if you forget to delete dynamically created objects then resources can be lost. If you use the wrong form of delete, forget to use delete [] for an array and just use the plain scalar form of delete for example, then again things will not be done correctly (in the example not all objects will be properly destroyed).

This is why most of us try not to use such techniques on an ad-hoc basis and if we do then we wrap their usage in smart resource management types such as smart pointer types that help manage the cleanup automatically.

A good example is that I rarely use dynamically allocated built in arrays these days. I prefer to use std::vector (vector here means single dimension array) instead as it adds many useful features such as automatically growing as needed and when an instance is destroyed it destroys its element objects. For strings I use std::string for similar reasons. Under the covers of course std::vector and std::string types are using dynamic allocation. For std::string include the string header. For std::vector include the vector header. Note that std::vector is a class template and std::string is a specialisation (specific instance) of the std::basic_string class template for strings with characters of type char. Here is how the example would look using these types:

   unsigned int n(0);
   std::cout << "Enter size of array: ";
   std::cin >> n;

   // Create a vector of n empty strings
   // Note: each string created using default constructor
   std::vector<std::string> strings(n);

   // Do stuff with the vector
   strings[0] = "string 0";
   strings[1] = "string 1";
   strings[n-1] = "string n-1";

   // Done with strings vector.
   // No need to delete as std::string and std::vector
   // clean up their resources when they are destroyed

However, because std::vector types grow automatically this brings up the interesting possibility that the user does not have to specify how many names they want in advance, some other mechanism can be used to determine when they are done entering names - they enter an empty name for example. In this case the vector is being extended all the time so we use the push_back operation rather than try accessing non-existent elements using array subscripting (a very bad idea):

   // Create an empty vector of strings
   // Note: vector contains no strings to start with
   std::vector<std::string> strings;

   // Fill the vector with user's strings
   // until they enter an empty string:
   std::string user_name;
   std::cout << "Enter names, blank to quit:\n";
   do
   {
       std::getline(std::cin,user_name);
       if ( std::cin && !user_name.empty() )
       {
         strings.push_back( user_name );
       }
   }
   while ( std::cin && !user_name.empty() );

   // Done with strings vector.
   // No need to delete as std::string and std::vector
   // clean up their resources when they are destroyed

I use a useful standalone getline function (declared in the string header) to read a line of characters from the user (i.e. from std::cin) into a std::string object. Each string read from the user is pushed to the back of the vector (the back is the end, the front is the start), extending its size by 1 element each time. If there is a problem with the cin stream or the user enters an empty line (i.e. just hits enter) then nothing is pushed to the vector and the loop terminates.

Note that you can access the characters of a std::string as a C string by calling the string's c_str operation:

   for ( int i(0); i!=strings.size(); ++i )
   {
       std::cout << strings[i].c_str() << '\n';
   }

OK so the above is a little silly as std::string provides a stream insertion operator (operator<<) so there is no need to convert the string characters to a C string, but it demonstrates the usage, and is useful when working with functions that take C style strings.

The C++ standard library types such as std::vector and std::string types generally throw exceptions in the case of errors such as running out of memory.

Please note that all the example code snippets shown are just that - examples. They are not intended to be complete or of production quality but just to illuminate the text of the answer. As such I have left off most error checking and they may even contain the odd mistake or typo for which I apologise.

For more information on std::vector, std::string and other C++ standard library facilities see for example "The C++ Standard Library A Tutorial and Reference" by Nicolai M. Josuttis. See "Effective C++" by Scott Meyers, especially the "Memory Management" chapter for more on dynamic memory usage and abusage.  

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.