Выбрать главу

invalid string position

The at( ) member throws an object of class out_of_range, which derives (ultimately) from std::exception. By catching this object in an exception handler, you can take appropriate remedial actions such as recalculating the offending subscript or growing the array. Using string::operator[]( ) gives no such protection and is as dangerous as char array processing in C.[34] 

Strings and character traits

The program Find.cpp earlier in this chapter leads us to ask the obvious question: Why isn’t case-insensitive comparison part of the standard string class? The answer provides interesting background on the true nature of C++ string objects.

Consider what it means for a character to have "case." Written Hebrew, Farsi, and Kanji don’t use the concept of upper- and lowercase, so for those languages this idea has no meaning. It would seem that if there were a way to designate some languages as "all uppercase" or "all lowercase," we could design a generalized solution. However, some languages that employ the concept of "case" also change the meaning of particular characters with diacritical marks: for example, the cedilla in Spanish, the circumflex in French, and the umlaut in German. For this reason, any case-sensitive collating scheme that attempts to be comprehensive will be nightmarishly complex to use.

Although we usually treat the C++ string as a class, this is really not the case. The string type is actually a specialization of a more general constituent, the basic_string< > template. Observe how string is declared in the standard C++ header file:[35] 

typedef basic_string<char> string;

To really understand the nature of the string class, it’s helpful to delve a bit deeper and look at the template on which it is based. Here’s the declaration of the basic_string< > template:.

template<class charT,

  class traits = char_traits<charT>,

  class allocator = allocator<charT> >

  class basic_string;

In Chapter 5, we examine templates in great detail (much more than in Chapter 16 of Volume 1). For now, the main thing to notice about the two previous declarations is that the string type is created when the basic_string template is instantiated with char. Inside the basic_string< > template declaration, the line.

class traits = char_traits<charT>,

tells us that the behavior of the class made from the basic_string< > template is specified by a class based on the template char_traits< >. Thus, the basic_string< > template provides for cases in which you need string-oriented classes that manipulate types other than char (wide characters, for example). To do this, the char_traits< > template controls the content and collating behaviors of a variety of character sets using the character comparison functions eq( ) (equal), ne( ) (not equal), and lt( ) (less than) upon which the basic_string< > string comparison functions rely.

This is why the string class doesn’t include case-insensitive member functions: that’s not in its job description. To change the way the string class treats character comparison, you must supply a different char_traits< > template, because that defines the behavior of the individual character comparison member functions.

You can use this information to make a new type of string class that ignores case. First, we’ll define a new case-insensitive char_traits< > template that inherits from the existing template. Next, we’ll override only the members we need to change to make character-by-character comparison case insensitive. (In addition to the three lexical character comparison members mentioned earlier, we’ll also have to supply a new implementation for the char_traits functions find( ) and compare( ).) Finally, we’ll typedef a new class based on basic_string, but using the case-insensitive ichar_traits template for its second argument.

//: C03:ichar_traits.h

// Creating your own character traits

#ifndef ICHAR_TRAITS_H

#define ICHAR_TRAITS_H

#include <cassert>

#include <cctype>

#include <cmath>

#include <ostream>

#include <string>

using std::toupper;

using std::tolower;

using std::ostream;

using std::string;

using std::char_traits;

using std::allocator;

using std::basic_string;

struct ichar_traits : char_traits<char> {

  // We'll only change character-by-

  // character comparison functions

  static bool eq(char c1st, char c2nd) {

    return toupper(c1st) == toupper(c2nd);

  }

  static bool ne(char c1st, char c2nd) {

    return !eq(c1st, c2nd);

  }

  static bool lt(char c1st, char c2nd) {

    return toupper(c1st) < toupper(c2nd);

  }

  static int compare(const char* str1,

    const char* str2, size_t n) {

    for(size_t i = 0; i < n; i++) {

      if(str1 == 0)

        return -1;

      else if(str2 == 0)

        return 1;

      else if(tolower(*str1) < tolower(*str2))

        return -1;

      else if(tolower(*str1) > tolower(*str2))

        return 1;

      assert(tolower(*str1) == tolower(*str2));

      str1++; str2++; // Compare the other chars

    }

    return 0;

  }

  static const char* find(const char* s1,

    size_t n, char c) {

    while(n-- > 0)

      if(toupper(*s1) == toupper(c))

        return s1;

      else

        ++s1;

    return 0;

  }

};

typedef basic_string<char, ichar_traits> istring;

inline ostream& operator<<(ostream& os, const istring& s) {

  return os << string(s.c_str(), s.length());

}

#endif // ICHAR_TRAITS_H  ///:~

We provide a typedef named istring so that our class will act like an ordinary string in every way, except that it will make all comparisons without respect to case. For convenience, we’ve also provided an overloaded operator<<( ) so that you can print istrings. Here’s an example:.

вернуться

34

Alert: For the safety reasons mentioned, the C++ Standards Committee is considering a proposal to redefine string::operator[] to behave identically to string::at() for C++0x.

вернуться

35

Your implementation can define all three template arguments here. Because the last two template parameters have default arguments, such a declaration is equivalent to what we show here.