Je remarque que dans ma copie de la référence SGI STL, il y a une page sur les traits de caractère mais je ne vois pas comment ils sont utilisés. Remplacent-ils les fonctions string.h? Ils ne semblent pas être utilisés par std::string
, par exemple , le length()
méthode sur std::string
ne fait pas usage de traits de caractère length()
méthode. Pourquoi les traits de caractère existent-ils et sont-ils utilisés dans la pratique?
Réponse
Trop de publicités?Les traits de caractère sont une composante extrêmement importante des cours d'eau et les chaînes de bibliothèques, parce qu'ils permettent le flux de la chaîne de classes afin de séparer la logique de ce que sont les caractères stockés dans la logique de ce que les manipulations doivent être effectuées sur ces personnages.
Pour commencer, la valeur par défaut des traits de caractère de classe, char_traits<T>
, est largement utilisé dans la norme C++. Par exemple, il n'y a pas de classe appelé std::string
. Plutôt, il y a un modèle de classe std::basic_string
qui ressemble à ceci:
template <typename charT, typename traits = char_traits<charT> >
class basic_string;
Ensuite, std::string
est défini comme
typedef basic_string<char> string;
De même, le flux standard sont définis comme
template <typename charT, typename traits = char_traits<charT> >
class basic_istream;
typedef basic_istream<char> istream;
Alors, pourquoi ces classes structuré comme ils sont? Pourquoi devrions-nous être à l'aide d'un étrange traits de classe comme un argument de modèle?
La raison en est que, dans certains cas, nous pourrions avoir une chaîne comme std::string
, mais avec des propriétés légèrement différentes. Un exemple classique de ce est si vous souhaitez stocker des chaînes de caractères dans une manière qui ignore la casse. Par exemple, je pourrais avoir besoin de faire une chaîne de caractères appelée CaseInsensitiveString
tel que je peux avoir
CaseInsensitiveString c1 = "HI!", c2 = "hi!";
if (c1 == c2) { // Always true
cout << "Strings are equal." << endl;
}
C'est, je peux avoir une chaîne où les deux chaînes ne différant que par leur sensibilité à la casse sont contre l'égalité.
Maintenant, supposons que la bibliothèque standard auteurs ont conçu des chaînes sans l'aide de traits. Cela signifierait que j'aurais de la bibliothèque standard, une force immense chaîne de classe qui a été tout à fait inutile dans mon cas. Je ne pouvais pas réutiliser une grande partie du code pour cette chaîne de caractères de la classe, puisque les comparaisons toujours du travail à l'encontre de la façon dont je voulais travailler. Mais à l'aide de traits, il est possible de réutiliser le code qui anime std::string
pour obtenir une casse de chaîne.
Si vous tirez une copie du C++ standard ISO et de regarder la définition du mode de fonctionnement de la chaîne des opérateurs de comparaison du travail, vous verrez qu'ils sont tous définis dans les termes de l' compare
fonction. Cette fonction est à son tour défini par l'appel
traits::compare(this->data(), str.data(), rlen)
où str
est la chaîne que vous êtes en comparant et en rlen
est la plus petite des deux longueurs de chaîne. C'est tout à fait intéressante, car elle signifie que la définition de l' compare
utilise directement l' compare
fonction exportée par les traits de personnalité de type spécifié comme paramètre de modèle! Par conséquent, si l'on définit une nouvelle classe de traits, puis définissez compare
, de sorte qu'il compare les caractères de cas insensiblement, nous pouvons construire une chaîne de classe qui se comporte comme std::string
, mais traite les choses au cas insensiblement!
Voici un exemple. Nous héritons d' std::char_traits<char>
pour obtenir le comportement par défaut pour toutes les fonctions que nous n'écris pas:
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;
}
};
(Notez que j'ai aussi défini eq
et lt
ici, qui permettent de comparer les caractères de l'égalité et de la moins-que, respectivement, et ensuite définie en compare
dans les termes de cette fonction).
Maintenant que nous avons cette classe de traits, on peut définir l' CaseInsensitiveString
trivialement comme
typedef std::basic_string<char, CaseInsensitiveTraits> CaseInsensitiveString;
Et le tour est joué! Nous avons maintenant une chaîne qui traite tout cas insensiblement!
Bien sûr, il y a d'autres raisons en plus de cela pour l'utilisation de traits. Par exemple, si vous souhaitez définir une chaîne qui utilise certains sous-jacent type de caractère de taille fixe, alors vous pouvez vous spécialiser char_traits
sur ce type et puis faire des chaînes de ce type. Dans l'API Windows, par exemple, il y a un type TCHAR
que se soit un étroit ou large personnage en fonction de ce que les macros que vous définissez lors du prétraitement. Vous pouvez alors faire des chaînes de TCHAR
s en écriture
typedef basic_string<TCHAR> tstring;
Et maintenant, vous avez une chaîne de TCHAR
s.
Dans tous ces exemples, l'avis que nous venons de définir certains traits de la classe (ou l'un de ceux qui existent déjà) en tant que paramètre à certains type de modèle afin d'obtenir une chaîne de caractères de ce type. Le point entier de ce que l' basic_string
de l'auteur a juste besoin de spécifier comment utiliser les traits de caractère et nous comme par magie peut rendre l'utilisation de nos traits de caractère, plutôt que la valeur par défaut pour obtenir des chaînes qui ont certaines nuances ou caprice ne fait pas partie du type de chaîne par défaut.
Espérons que cette aide!
EDIT: @phooji souligné, cette notion de traits n'est pas seulement utilisé par le TSL, ni est-il spécifique à C++. En tant que complètement sans vergogne de l'auto-promotion, un temps, j'ai écrit une mise en œuvre d'un ternaire arbre de recherche (type de radix arbre décrit ici) qui utilise des traits de stocker des chaînes de tout type et de l'aide quel que soit le type de comparaison que le client veut store. Il pourrait être une lecture intéressante si vous voulez voir un exemple de cas où il est utilisé dans la pratique.
EDIT: En réponse à votre demande std::string
ne pas utiliser traits::length
, il s'avère que dans quelques endroits. Plus particulièrement, lorsque vous construisez une std::string
d'un char*
C-chaîne de style, la nouvelle longueur de la chaîne est obtenue en appelant traits::length
sur cette chaîne. Il semble qu' traits::length
est principalement utilisé pour traiter les C-style séquences de caractères, qui sont le "plus petit dénominateur commun" des chaînes de caractères en C++, tout en std::string
est utilisé pour travailler avec les chaînes de l'arbitraire contenu.