Homework 5: Assignable Strings

Educational Objectives: After completing this assignment the student should have the following knowledge, ability, and skills:

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

I/O Operator Expectations

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 with exactly the characters of the token removed - all characters (including clear space) past the last token character must remain in the stream.

Similarly, the overloaded output operator for a type T has this prototype:

std::ostream& operator << (std::ostream& os, const T& t);

and its implementation should output a token representation of the object of type T. The two I/O operators should be mutually consistent in the sense that

os << t;

followed by

is >> t;

on the token will result in reconstructing the object t as it was before output, and

is >> t;

followed by

os << t;

will result in the same token representation of a T object.

Procedural Requirements

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

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

    hw5/fastring.cpp
    submitscripts/hw5submit.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 hw5submit.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 Length() <= Size() == 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. 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 method Cstr() returns the address of the underlying C-string as a pointer-to-constant. Cstr() is useful in supplying C-string arguments to functions such as those in the cstring library and the std::fstream method open(const char*) used to open files.

  4. AString should have these methods that are versions of the bracket operator:

    1. char& operator [] (size_t index);
    2. const char& operator [] (size_t index) const;
    The bracket operator returns (by reference) the index index character in the string. It may seem strange that we have two versions of the bracket operator: one is a const member function that returns a const reference, and the other is not const and returns an ordinary (mutable) reference. These are coded in the same way. The purpose of the const version is to allow the bracket operator to be used on a constant AString object for (read only) element access.

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

  6. The overloaded I/O operators should conform to the standard expectations expressed above for type T = AString.

  7. 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 last legally accessable index size_ - 1. (You may also want to be sure size_ > 0 before doing this. In this last size_ == 0 case, where no memory has been allocated, there is no choice but to exit in a failure condition.)
    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(): you may implement your own test loop or call a cstring library function here.

Hints