¿Cuál es el punto de los Rasgos de Carácter de STL?


Noto que en mi copia de la referencia SGI STL, hay una página sobre Rasgos de Carácter, pero no puedo ver cómo se usan? ¿Reemplazan la cuerda?h funciones? No parecen ser utilizados por std::string, por ejemplo, el método length() en std::string no hace uso del método Character Traits length(). ¿Por qué existen los Rasgos de Carácter y se usan alguna vez en la práctica?

Author: Eitan T, 2011-03-16

1 answers

Los rasgos de carácter son un componente extremadamente importante de las bibliotecas de secuencias y cadenas porque permiten que las clases de secuencia/cadena separen la lógica de qué caracteres se almacenan de la lógica de qué manipulaciones se deben realizar en esos caracteres.

Para empezar, la clase por defecto character traits, char_traits<T>, se usa ampliamente en el estándar de C++. Por ejemplo, no hay ninguna clase llamada std::string. Más bien, hay una clase plantilla std::basic_string que se ve así:

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

Entonces std::string se define como

typedef basic_string<char> string;

Del mismo modo, los flujos estándar se definen como

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

typedef basic_istream<char> istream;

Entonces, ¿por qué estas clases están estructuradas como están? ¿Por qué deberíamos usar una clase de rasgos extraños como argumento de plantilla?

La razón es que en algunos casos podríamos querer tener una cadena como std::string, pero con algunas propiedades ligeramente diferentes. Un ejemplo clásico de esto es si desea almacenar cadenas de una manera eso ignora el caso. Por ejemplo, podría querer hacer una cadena llamada CaseInsensitiveString tal que pueda tener

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

Es decir, puedo tener una cadena donde dos cadenas que difieren solo en su sensibilidad de mayúsculas y minúsculas se comparan iguales.

Ahora, supongamos que los autores de la biblioteca estándar diseñaron cadenas sin usar traits. Esto significaría que tendría en la biblioteca estándar una clase string inmensamente poderosa que era completamente inútil en mi situación. No pude reutilizar gran parte del código para esta clase de cadena, ya que las comparaciones siempre funcionarían en contra de cómo quería que funcionaran. Pero al usar traits, en realidad es posible reutilizar el código que conduce std::string para obtener una cadena que no distingue entre mayúsculas y minúsculas.

Si obtiene una copia del estándar ISO de C++ y observa la definición de cómo funcionan los operadores de comparación de la cadena, verá que todos están definidos en términos de la función compare. Esta función se define a su vez llamando a

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

Donde str es la cadena estás comparando con y rlen es la menor de las dos longitudes de cadena. Esto es realmente bastante interesante, porque significa que la definición de compare utiliza directamente la función compare exportada por el tipo de rasgos especificado como un parámetro de plantilla! En consecuencia, si queremos definir una nueva clase de rasgos, a continuación, definir compare, de modo que compara los caracteres a mayúsculas-minúsculas, podemos construir una cadena de clase que se comporta como std::string, pero trata de cosas a mayúsculas-minúsculas!

Aquí hay un ejemplo. Heredamos de std::char_traits<char> para obtener el comportamiento predeterminado para todas las funciones que no escribimos:

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

(Observe que también he definido eq y lt aquí, que comparan caracteres para igualdad y menor que, respectivamente, y luego definido compare en términos de esta función).

Ahora que tenemos esta clase de rasgos, podemos definir CaseInsensitiveString trivialmente como

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

Y voila! Ahora tenemos una cadena que trata todo a mayúsculas-minúsculas!

Por supuesto, hay son otras razones además de esto para el uso de rasgos. Por ejemplo, si desea definir una cadena que utilice algún tipo de carácter subyacente de tamaño fijo, puede especializarse char_traits en ese tipo y luego crear cadenas a partir de ese tipo. En la API de Windows, por ejemplo, hay un tipo TCHAR que es un carácter estrecho o ancho dependiendo de las macros que establezca durante el preprocesamiento. A continuación, puede hacer cadenas de TCHARs escribiendo

typedef basic_string<TCHAR> tstring;

Y ahora tienes una cadena de TCHAR s.

En todos estos ejemplos, observe que solo definimos algunas clases de traits (o usamos una que ya existía) como parámetro para algún tipo de plantilla con el fin de obtener una cadena para ese tipo. El punto de esto es que el autor de basic_string solo necesita especificar cómo usar los rasgos y mágicamente podemos hacer que usen nuestros rasgos en lugar de los predeterminados para obtener cadenas que tienen algún matiz o peculiaridad que no forman parte del tipo de cadena predeterminado.

Espero que esto ayuda!

EDIT : Como @phooji señaló, esta noción de rasgos no solo es utilizada por el STL, ni es específica de C++. Como una autopromoción completamente desvergonzada, hace un tiempo escribí una implementación de un árbol de búsqueda ternaria (un tipo de árbol radix descrito aquí) que usa traits para almacenar cadenas de cualquier tipo y usando cualquier tipo de comparación que el cliente quiera que almacenen. Podría ser una lectura interesante si desea ver un ejemplo de donde esta se utiliza en la práctica.

EDITAR: En respuesta a tu afirmación de que std::string no usa traits::length, resulta que sí lo hace en algunos lugares. En particular, cuando se construye un std::string a partir de una cadena de estilo C char*, la nueva longitud de la cadena se deriva llamando a traits::length en esa cadena. Parece que traits::length se usa principalmente para tratar con secuencias de caracteres de estilo C, que son el "mínimo denominador común" de cadenas en C++, mientras que std::string se usa para trabajar con cadenas de caracteres arbitrarios contenido.

 145
Author: templatetypedef,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-16 02:20:06