There are a number of places in a typical
C++ program where you'll embed string literals in your
source files. For example, if you write
cout << "This is a string literal";
the compiler will place these characters in the default data segment and use the address of the first character as a const char pointer when it creates the instructions for this line.
If you're writing a Windows application, you can retrieve text from a string table for this purpose. DOS programs, on the other hand, will typically embed error and prompt messages as string literals.
Unfortunately, sprinkling string literals all over your source files can consume a significant amount of space in the default data segment. In addition, if you decide to make changes to any of these strings, you may have to search through many source files, make the changes, and then recompile each file.
In this article, we'll show you how to make strings part
of your program's classes or structs and then initialize
those strings from one or two source files. If you segregate all
the string literals for your program this way, you may eliminate
multiple copies of some strings. In addition, you'll be
able to change the strings themselves or their location in memory
more easily.
Most of the time, programmers don't think about string literals until they have a problem or need to make a change to the text in the string. However, instead of simply entering text strings as you need them in a source file, the technique we'll show you requires you to think about what strings you'll use within a given class.
For example, if you're creating a class named ProcessStatus that will regularly print the status of a process, you'll first consider what the status messages will be. Then, you'll simply add a static const char pointer for each message as a member of the ProcessStatus class, as shown in Figure A.
Figure A - The ProcessStatus class declares each status message as a static const char pointer.
class ProcessStatus { public: ProcessStatus(); static const char* okText; static const char* error1Text; static const char* error2Text; // remainder of the class };
Obviously, these strings don't need to be members of a classyou could simply create them as static global variables. However, if you do make them class members, you won't have to worry about name conflicts that may occur if you use similar types of strings from different classes.
Now, you can create one source file to implement the ProcessStatus class's constructor (and any of its other member functions). Then, in a separate source file, you'll initialize the strings for this class and any other classes that declare static string literals, as shown in Figure B.
Figure B - You can initialize the static strings for the ProcessStatus class from another file.
#include "process.h" // The ProcessStatus // declaration const char* ProcessStatus::okText = "- OK -"; const char* ProcessStatus::error1Text = "Buffer Overflow"; const char* ProcessStatus::error2Text = "Invalid Data"; // You can initialize the static string members // for another class too! const char* HelpDisplay::mainMenuHelp = "Select a menu"; const char* HelpDisplay::menu1Help = "Select a file option"; const char* HelpDisplay::menu2Help = "Select an edit task";
The first problem with using string literals in a random way is duplication: You may find that you're using the same text in more than one place. If the text strings are very long, or if the same string appears many times, this can mean a significant waste of memory.
If you're thinking that you can achieve the same results by using the Merge Duplicate Strings option (-d from the command line), you're partially correct. This option will eliminate duplicates within a given source file.
Unfortunately, if the same string appears in different source files, the linker won't eliminate the duplicates. For the linker to do so would require that it change the surrounding code based on the memory model you're using. (If you're using the small memory model, the linker would need to change a near pointer to a string into a far pointer to the string's duplicate in a different module.)
However, you may not be concerned about the memory waste that
occurs when you use the same string literal more than once. If
this is the case, you should consider that moving all the string
literals to one file will make your application's text
much easier to locate and update. (If you're considering
shipping an application to another country, this technique makes
it easier to keep different versions of the string source file
for each language.)
In addition to the problem of duplicates, some applications use
so many string literals that the string text uses up a significant
amount of space in the default data segment. In fact, you may
see the error message
Group DGROUP exceeds 64K
or
Segment DATA exceeds 64K
from the linker if the combination of string literals and initialized data in the data segment becomes too large.
One way to solve this problem is to move your string literals
to a separate segment. If you make all the string literals static
class members or static global variables and initialize them from
a single source file, you'll only need to compile that
one file with special segment name, class, and group settings.
See the article Another C++ DOS Design tip - Placing string literals in another data segment, for more information.
Now let's create a simple program that implements a class's
member functions in the application's main source file.
We'll create a second source file to initialize the class's
static string members. Before you start, create a new directory
for this project by entering
md \borlandc\clastext
at the DOS command prompt.
Next, launch the Borland C++ Integrated Development Environment (IDE) 3.1 for DOS. (You can also use this technique with version 4.0, but you'll use different steps to create the project file.)
When the IDE's menu bar and desktop appear, choose Open Project... from the Project menu. In the Open Project File entry field of the resulting dialog box, enter \BORLANDC\CLASTEXT.PRJ and then click OK.
Now, choose New from the File menu. In the edit window that appears, enter the code from Listing A.
Listing A: PUPPET.H
class Puppet { public: Puppet(); void showStrings(); static const char* string2; static const char* string3; private: const char* string1; };
When you finish entering the declaration of the Puppet class, choose Save from the File menu. In the Save File As entry field of the resulting dialog box, enter the filename PUPPET.H. Choose Close from the Window menu to close the edit window for the PUPPET.H file.
Next, use the same method to enter the code from Listing B and Listing C. When you finish entering the code for those files, save them as PINOCHIO.CPP and STRMBOLI.CPP respectively.
Listing B: PINOCHIO.CPP
#include "puppet.h" #include <iostream.h> Puppet::Puppet() { string1 = "Guitar"; } void Puppet::showStrings() { cout << string1 << endl; cout << string2 << endl; cout << string3 << endl; } int main() { Puppet marion; marion.showStrings(); return 0; }
In the source file PINOCHIO.CPP, we first define the constructor for the Puppet class. In this constructor, we initialize the string1 variable (the Guitar string) in the traditional manner. However, this file doesn't initialize the other two string variables.
Listing C: STRMBOLI.CPP
#include "puppet.h" const char* Puppet::string2 = "Purse 1"; const char* Puppet::string3 = "Purse 2";
The source file STRMBOLI.CPP contains the string literals we use to initialize the other string variables of the Puppet class (the Purse strings). This file can initialize and hold the Purse strings for the Puppet class only because they're public static class members. To initialize private members like string1, you'll either need to keep the string literal in the original source file for the class or add a new member function that initializes the string variable from another module.
After saving the two source code files, bring the Project window to the front by choosing Project from the Window menu. When the Project window appears, choose Add Item... from the Project menu.
In the Name entry field of the Add To Project List dialog box, enter PINOCHIO.CPP and click Add. When the dialog box reappears, enter STRMBOLI.CPP and click Add. When the dialog box reappears, click Done.
Now, build the application by choosing Make from the Compile menu. When the IDE finishes compiling and linking the application, choose Quit from the File menu to quit the IDE.
To run the application, enter CLASTEXT at the DOS command
prompt. When the application runs, you should see all three of
the following strings
Guitar Purse 1 Purse 2
appear on the screen.
Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.