1. ホーム
  2. c++

[解決済み] STL Character Traitsのポイントは何ですか?

2023-05-22 18:42:37

質問

私の持っているSGI STLリファレンスには、Character Traitsについてのページがありますが、これがどのように使用されるのかがわかりません?これらはstring.hの関数を置き換えるのでしょうか?それらは std::string には使われていないようです。 length() メソッドで std::string はキャラクター特性を利用しません。 length() メソッドを使用しています。なぜCharacter Traitsが存在するのか、そして実際に使用されることはあるのでしょうか?

どのように解決するのですか?

Character traits は、ストリームや文字列ライブラリの非常に重要なコンポーネントです。 どの文字が格納されているか のロジックから これらの文字に対してどのような操作を行うべきかというロジックから

そもそも、デフォルトのキャラクタの特性クラスである char_traits<T> は、C++ 標準では広範囲に使用されています。 例えば std::string . むしろ、クラステンプレート std::basic_string のようなもので、このようになります。

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

では std::string は次のように定義されます。

typedef basic_string<char> string;

同様に、標準的なストリームは次のように定義されます。

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

typedef basic_istream<char> istream;

では、なぜこれらのクラスはこのような構造になっているのでしょうか? なぜ奇妙な traits クラスをテンプレートの引数として使用しなければならないのでしょうか?

のような文字列を持ちたい場合があるからです。 std::string のような文字列でありながら、若干異なるプロパティを持ちたい場合があるからです。 この典型的な例としては、大文字小文字を無視した形で文字列を格納したい場合があります。 例えば、文字列を CaseInsensitiveString という文字列を作り、それを

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

つまり、大文字小文字の違いだけが異なる2つの文字列が等しく比較されるような文字列ができるんです。

さて、標準ライブラリの作者がtraitを使わずに文字列を設計したとします。 これは、私の状況では全く役に立たない、非常に強力な文字列クラスが標準ライブラリの中にあることを意味します。 この文字列クラスのコードの多くは再利用できません。なぜなら、比較は常に私が望む方法とは逆に動作してしまうからです。 しかし、traitを使うことで、実際に std::string を駆動して大文字小文字を区別しない文字列を取得するコードを再利用することが実際に可能です。

C++のISO標準を引っ張り出してきて、文字列の比較演算子がどのように動作するかの定義を見てみると、それらはすべて compare 関数で定義されていることがわかります。 この関数は、順番に

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

ここで str は比較対象の文字列で rlen は 2 つの文字列の長さのうち小さい方です。 これは実に興味深いことで、つまりは compare の定義が直接 compare 関数を直接使用します。 その結果、もし新しい traits クラスを定義し、そのクラスに対して compare を定義し、それが大文字小文字を区別せずに文字を比較するようにすれば、 文字列クラスを作ることができます。 std::string と同じように振る舞いますが、大文字小文字を区別せずに扱います。

以下はその例です。 私たちは std::char_traits<char> を継承して、私たちが書かない全ての関数のデフォルトの振る舞いを取得しています。

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;
    }
};

(私はまた eqlt は、それぞれ文字を等しいかどうか、より小さいかどうかを比較するもので、次に compare をこの関数の観点で定義しています)。

この traits クラスができたので、次のように定義できます。 CaseInsensitiveString として定義できます。

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

そして、出来上がりです。 これで大文字小文字を区別しない文字列が完成です!

もちろん、これ以外にもtraitを使用する理由はあります。 例えば、ある固定サイズの文字型を使用する文字列を定義したい場合、特殊化した char_traits を特殊化し、その型から文字列を作ることができます。 例えばWindowsのAPIでは、このような型に TCHAR という型があり、前処理で設定したマクロによって幅の狭い文字になったり広い文字になったりする。 この型から文字列を作ることができる。 TCHAR を書くことによって

typedef basic_string<TCHAR> tstring;

そして、今度は文字列の TCHAR s.

これらの例のすべてにおいて、ある型の文字列を取得するために、あるテンプレート型のパラメータとしてある形質クラス(または既に存在するものを使用)を定義していることに注意してください。 これの要点は basic_string の作者が traits の使用方法を指定するだけで、デフォルトの文字列型にはないニュアンスや癖のある文字列を取得するために、デフォルトではなく私たちの traits を使用するように魔法のようにできることです。

これが役に立つことを願っています!

EDIT : phoojiさんが指摘されているように、このtraitの概念はSTLだけで使われているわけでもなく、C++に特有なものでもないようです。 まったくもって恥知らずな自画自賛ですが、少し前に私は 三項探索木の実装 (基数木の一種 で説明されています。 で説明されています)、trait を使って任意の型の文字列を格納し、クライアントが格納することを望むあらゆる比較型を使用するものです。 これが実際に使用されている例を見たいのであれば、興味深い読み物かもしれません。

EDIT : というあなたの主張に対して std::string は使用しません。 traits::length を使用しませんが、いくつかの場所では使用することが判明しています。 最も顕著なのは std::string から char* C スタイルの文字列の場合、文字列の新しい長さは traits::length を呼び出すことで得られる。 どうやら traits::length は主に C スタイルの文字列を扱うために使用されます。 std::string は任意の内容の文字列を扱うために使用されます。