Was ist der Sinn von STL-Charakterzügen?

83

Ich stelle fest, dass in meiner Kopie der SGI STL-Referenz eine Seite über Charaktereigenschaften enthalten ist, aber ich kann nicht sehen, wie diese verwendet werden. Ersetzen sie die Funktionen string.h? Sie scheinen nicht von verwendet zu werden std::string, z. B. verwendet die length()Methode on std::stringnicht die Character Traits- length()Methode. Warum gibt es Charaktereigenschaften und werden sie jemals in der Praxis verwendet?

Matthew Smith
quelle

Antworten:

171

Zeichenmerkmale sind eine äußerst wichtige Komponente der Streams und String-Bibliotheken, da sie es den Stream- / String-Klassen ermöglichen, die Logik der gespeicherten Zeichen von der Logik der Manipulationen an diesen Zeichen zu trennen .

Zunächst wird die Standardklasse für Zeichenmerkmale char_traits<T>im C ++ - Standard häufig verwendet. Beispielsweise wird keine Klasse aufgerufen std::string. Es gibt vielmehr eine Klassenvorlage std::basic_string, die so aussieht:

template <typename charT, typename traits = char_traits<charT> >
    class basic_string;

Dann std::stringist definiert als

typedef basic_string<char> string;

In ähnlicher Weise sind die Standardströme definiert als

template <typename charT, typename traits = char_traits<charT> >
    class basic_istream;

typedef basic_istream<char> istream;

Warum sind diese Klassen so strukturiert, wie sie sind? Warum sollten wir eine seltsame Merkmalsklasse als Vorlagenargument verwenden?

Der Grund dafür ist, dass wir in einigen Fällen eine Zeichenfolge wie diese haben möchten std::string, jedoch mit etwas anderen Eigenschaften. Ein klassisches Beispiel hierfür ist, wenn Sie Zeichenfolgen so speichern möchten, dass Groß- und Kleinschreibung ignoriert wird. Zum Beispiel möchte ich vielleicht eine Zeichenfolge mit dem Namen CaseInsensitiveStringerstellen, die ich haben kann

CaseInsensitiveString c1 = "HI!", c2 = "hi!";
if (c1 == c2) {  // Always true
    cout << "Strings are equal." << endl;
}

Das heißt, ich kann eine Zeichenfolge haben, bei der zwei Zeichenfolgen, die sich nur in ihrer Groß- / Kleinschreibung unterscheiden, gleich verglichen werden.

Angenommen, die Autoren der Standardbibliothek haben Zeichenfolgen ohne Verwendung von Merkmalen entworfen. Dies würde bedeuten, dass ich in der Standardbibliothek eine immens mächtige String-Klasse hätte, die in meiner Situation völlig nutzlos war. Ich konnte einen Großteil des Codes für diese Zeichenfolgenklasse nicht wiederverwenden, da Vergleiche immer so funktionieren, wie ich sie haben wollte. Durch die Verwendung von Merkmalen ist es jedoch tatsächlich möglich, den Laufwerkscode wiederzuverwenden std::string, um eine Zeichenfolge ohne Berücksichtigung der Groß- und Kleinschreibung zu erhalten.

Wenn Sie eine Kopie des C ++ - ISO-Standards aufrufen und sich die Definition der Funktionsweise der Vergleichsoperatoren der Zeichenfolge ansehen, werden Sie feststellen, dass sie alle in Bezug auf die compareFunktion definiert sind. Diese Funktion wird wiederum durch Aufruf definiert

traits::compare(this->data(), str.data(), rlen)

Wo strist die Zeichenfolge, mit der Sie vergleichen, und wo rlenist die kleinere der beiden Zeichenfolgenlängen? Dies ist eigentlich sehr interessant, da die Definition von comparedirekt die compareFunktion verwendet, die durch den als Vorlagenparameter angegebenen Merkmalstyp exportiert wird! Wenn wir also eine neue Merkmalsklasse definieren und diese compareso definieren , dass Zeichen ohne Berücksichtigung der Groß- und Kleinschreibung verglichen werden, können wir eine Zeichenfolgenklasse erstellen, die sich genauso verhält std::string, die Dinge jedoch ohne Berücksichtigung der Groß- und Kleinschreibung behandelt!

Hier ist ein Beispiel. Wir erben von std::char_traits<char>, um das Standardverhalten für alle Funktionen zu erhalten, die wir nicht schreiben:

class CaseInsensitiveTraits: public std::char_traits<char> {
public:
    static bool lt (char one, char two) {
        return std::tolower(one) < std::tolower(two);
    }

    static bool eq (char one, char two) {
        return std::tolower(one) == std::tolower(two);
    }

    static int compare (const char* one, const char* two, size_t length) {
        for (size_t i = 0; i < length; ++i) {
            if (lt(one[i], two[i])) return -1;
            if (lt(two[i], one[i])) return +1;
        }
        return 0;
    }
};

(Beachten Sie, dass ich auch eqund lthier definiert habe, die Zeichen auf Gleichheit bzw. weniger als vergleichen und dann comparein Bezug auf diese Funktion definiert werden.)

Jetzt, wo wir diese Merkmalsklasse haben, können wir CaseInsensitiveStringtrivial definieren als

typedef std::basic_string<char, CaseInsensitiveTraits> CaseInsensitiveString;

Und voila! Wir haben jetzt eine Zeichenfolge, die alles ohne Berücksichtigung der Groß- und Kleinschreibung behandelt!

Natürlich gibt es neben diesem Grund noch andere Gründe für die Verwendung von Merkmalen. Wenn Sie beispielsweise eine Zeichenfolge definieren möchten, die einen zugrunde liegenden Zeichentyp mit fester Größe verwendet, können Sie sich char_traitsauf diesen Typ spezialisieren und dann Zeichenfolgen aus diesem Typ erstellen. In der Windows-API gibt es beispielsweise einen Typ TCHAR, der entweder ein schmales oder ein breites Zeichen ist, je nachdem, welche Makros Sie während der Vorverarbeitung festgelegt haben. Sie können dann Zeichenfolgen aus TCHARs machen, indem Sie schreiben

typedef basic_string<TCHAR> tstring;

Und jetzt hast du eine Reihe von TCHARs.

Beachten Sie in all diesen Beispielen, dass wir gerade eine Merkmalklasse (oder eine bereits vorhandene) als Parameter für einen Vorlagentyp definiert haben, um eine Zeichenfolge für diesen Typ zu erhalten. Der springende Punkt dabei ist, dass der basic_stringAutor nur angeben muss, wie die Merkmale verwendet werden sollen, und wir können sie auf magische Weise dazu bringen, unsere Merkmale anstelle der Standardmerkmale zu verwenden, um Zeichenfolgen zu erhalten, deren Nuancen oder Eigenheiten nicht Teil des Standardzeichenfolgen-Typs sind.

Hoffe das hilft!

EDIT : Wie @phooji wies darauf hin, dieser Begriff der Züge ist einfach nicht von der STL verwendet, noch ist es spezifisch für C ++. Als völlig schamlose Eigenwerbung, schrieb ich vor einiger Zeit eine Implementierung eines ternären Suchbaumes (eine Art von Radix Baum hier beschrieben ) , dass Anwendungen Merkmale zu speichern Zeichenkette jeder Art und mit was auch immer Vergleich des Kunde will , sie zu speichern geben. Es könnte eine interessante Lektüre sein, wenn Sie ein Beispiel dafür sehen möchten, wo dies in der Praxis verwendet wird.

BEARBEITEN : Als Antwort auf Ihre Behauptung, std::stringdie nicht verwendet wird traits::length, stellt sich heraus, dass dies an einigen Stellen der Fall ist. Insbesondere wenn Sie eine Zeichenfolge std::stringaus einem char*C-Stil erstellen, wird die neue Länge der Zeichenfolge durch Aufrufen traits::lengthdieser Zeichenfolge abgeleitet. Es scheint, dass dies traits::lengthhauptsächlich verwendet wird, um Zeichenfolgen im C-Stil zu behandeln, die der "kleinste gemeinsame Nenner" von Zeichenfolgen in C ++ sind, während std::stringes verwendet wird, um mit Zeichenfolgen mit beliebigen Inhalten zu arbeiten.

templatetypedef
quelle
15
+ ∞
Chris Lutz
14
Scheint, als hätten Sie Ihrem Benutzernamen gerecht gemacht :) Vielleicht auch relevant: Viele der Boost-Bibliotheken verwenden Konzepte und Typ-Trait-Klassen, es ist also nicht nur die Standardbibliothek. Darüber hinaus werden ähnliche Techniken in anderen Sprachen ohne Verwendung von Vorlagen verwendet, siehe esoterisches Beispiel: ocaml.janestreet.com/?q=node/11 .
Phooji
2
schöne Struktur (Ternary Search Tree), aber ich möchte darauf hinweisen, dass Versuche auf verschiedene Arten "komprimiert" werden können: 1 / Verwenden von Zeichenbereichen, um auf ein Kind zu zeigen, anstatt auf einzelne Zeichen (der Gewinn ist offensichtlich), 2 / Pfadkomprimierung (Patricia Trees) und 3 / Buckets am Ende von Zweigen (dh verwenden Sie einfach ein sortiertes Array von Zeichenfolgen, solange weniger als K vorhanden sind). Durch die Kombination dieser (ich habe 1 und 3 kombiniert) wird der Speicherverbrauch drastisch reduziert, ohne die Geschwindigkeitsleistung um mehr als einen konstanten Faktor zu beeinträchtigen (und tatsächlich verringern die Buckets die Anzahl der Sprünge).
Matthieu M.
2
@ dan04: Versuchen Sie, eine Standardklasse / einen Standardalgorithmus für die Verwendung Ihrer Funktion zu erhalten.
Xeo
2
Also ... um es auf den Punkt zu bringen, Merkmale sind nur eine Art Schnittstelle, die von der basic_string-Klasse verwendet wird, um verschiedene Arten von Zeichen zu manipulieren, unabhängig davon, was sie wirklich sind, oder?
Virus721