Homework 4: Assignable Strings

Educational Objectives: Experience (1) creating proper types with dynamically allocated resources and (2) overloading operators for user-defined types.

Operational Objectives: Create a proper type AString whose objects can be used in place of C-strings in user programs. Overload the input and output operators for this type.

Deliverables: Two files astring.h and astring.cpp.

Strings as Proper Types

The goal for this assignment is to create a new type assignable string, AString, that is better behaved and easier for clients to use than low-level C-strings. A client program should be able to create an AString object of a desired length by a simple declaration (or call to operator new) with a parameter specifying how many characters the string object should hold. The client should be able to assign one AString object to another, return AString objects as values of functions, and pass AString objects to functions by value or reference. In addition, there should be a bracket operator that behaves like that for character arrays, except that it checks for out-of-bounds index errors by clients, and the AString member function Cstr() should facilitate the use of C string functions defined in string.h, provided the parameter type is const char*.

In essence, what is desired is that AString be a proper type while respecting the old assumption that strings are null-terminated. This latter feature causes a few compromises, but class AString should be fairly "bullet proof" at least as long as the client programmer is not intentionally mis-using the class.

Implementation Plan

The plan for implementing assignable strings is to maintain two private variables, one of type size_t and one of type char*. You choose the names for these variables in a sensible self-documenting manner. For this discussion, we will call them size_ and data_, respectively. The class has the responsibility for maintaining these variables in a consistent manner and for allocating and de-allocating resources (i.e., memory assigned to data_). In general, an AString object will have size_ + 1 bytes allocated and will maintain null-termination of data_, which leaves size_ elements of data_ where client characters can be stored. Constructors, copy constructor, and assignment operator should allocate memory appropriately (always checking for a failed allocation request, of course) and ensure that the C-string data_ is null-terminated. The destructor should de-allocate these resources.

Managing resources for client programs takes away the most aggravating and error-prone aspect of using C-strings. The implementation plan has other features that make client life even better, however: assignability and safety. Client programs can assign one AString object to another (this is part of being a proper type), and client attempts to access string elements with an out-of-bounds index are caught. This client mistake should result in an error message sent but not in program termination - just send a message through std::cerr and return data_[size_], the last element of the allocated memory (which should have the value '\0').

There are two methods Size() and Length() that return the size and length of the string represented by the AString object, respectively. Size() is the value stored in size_, whereas Length() is the number of characters in data_ before the first null character is encountered. Normally Size() and Length() would return the same number even though they are calculated in entirely different ways. However, a client could quite legally change an element of a string to '\0', effectively "shortening" the string and changing the value returned by Length().

The input and output operators should be overloaded and behave as clients have come to expect, and the client should be able to use an AString object in place of const char* parameters in library functions. For example, the client should be able to store a file name in an AString object filename and then open the file with a std::fstream object fs using the call fs.open(filename.Cstr()).

Procedural Requirements

  1. Create and work within a separate subdirectory cop3330/hw4. Review the COP 3330 rules found in Introduction/Work Rules.

  2. Begin by copying the following files from the course directory /home/courses/cop3330p/fall06/ into your hw4 directory:

    hw4/fastring.cpp
    submitscripts/hw4submit.sh
    area51/fastring_s.x
    area51/fastring_i.x
    

    The naming of these files uses the convention that _s are compiled for Sun/Solaris and _i are compiled for Intel/Linux. Use one of the sample client executables to experiment to get an understanding of how your program should behave.

  3. Create the class AString. Place the definition of the class and prototypes of overloaded operators in the file astring.h, and place the implementations of the class member functions and operators in the file astring.cpp.

  4. Test your class thoroughly and correct any errors in functionality. A test clent program fastring.cpp is supplied.

  5. Turn in two files astring.h and astring.cpp using the hw4submit.sh submit script.

    Warning: Submit scripts do not work on the program and linprog servers. Use shell.cs.fsu.edu to submit projects. If you do not receive the second confirmation with the contents of your project, there has been a malfunction.

Code Requirements and Specifications

  1. The type AString should be proper, that is, it should have constructors, destructor, copy constructor, and assignment operator that correctly manage resources (in this case, memory) for objects.

  2. AString should have three constructors (plus the copy constructor):

    1. A 0-parameter (default) constructor that creates a string of size 20
    2. A 1-parameter constructor AString (const char * cstr) that initializes to the size and data defined by the C-string argument cstr
    3. A 2-parameter constructor AString (size_t size, char init) that creates an AString object such that Size() == Length() == size and all elements are initialized to init

  3. AString should have these methods:

    1. size_t Size() const;
    2. size_t Length() const;
    3. char& operator [] (size_t index);
    4. const char& operator [] (size_t index) const;
    5. const char* Cstr() const;
    These are all const methods. Size() returns one less than the size of the allocated buffer (i.e., not counting the place in the buffer reserved for the null character). Length() returns the number of characters in the string before the first null character is encountered, i.e., the same value that would be returned by the C library function strlen(cstr) in the case of a C string cstr. The bracket operator returns (by reference) the index index character in the string. The method Cstr() returns the address of the underlying C-string as a pointer-to-constant.

  4. AString should have these operators overloaded:

    1. std::ostream& operator << (std::ostream& os, const AString& s);
    2. std::istream& operator >> (std::istream& is, AString& s);
    These do not require friend status.

  5. The overloaded input operator for a type T has this prototype:

    std::istream& operator >> (std::istream& is, T& t);
    

    and its implementation should always conform to these expectations:

    1. First skip clear space until a visible character is encountered
    2. Read visible characters until either the end of file is encountered or the next character cannot be interpreted as part of an object of type T. In the case of strings, this translates to: read visible characters until either end of file or a clearspace character is encountered. The string so read is called a "token".
    3. Construct a T object from this token and place it in t
    4. Return the stream is without removing any characters past the last token character

  6. Safety should be built in to the implementations:

    1. Bracket operator[]: A client's attempt to access an element whose index is out of range should be caught. Send an error message and return the element at the illegal (although defined) index size_.
    2. Input operator>>: If a string token has too many characters for the space allocated, the extra characters should be left in the stream.
    3. Method Length(): The loop used to implement Length() should terminate at the end of allocated space even if the data_ is not null-terminated. If this loop discovers that data_ is not null-terminated, it should send an appropriate error message.

Hints