Borland Online And The Cobb Group Present:


August, 1994 - Vol. 1 No. 8

C++ DOS Design tip - Initializing class strings in a separate source file

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.

Consider the source

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 class­­you 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";

As we mentioned earlier, there are two main benefits to using class strings: You'll be able to eliminate duplicate strings and modify most of your program's strings from one or two files, and you'll be able to more easily manage the memory your strings occupy. Let's take a closer look at both issues.

String cleaning

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.)

Remember memory issues

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.

Static strings in single file

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.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


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.