OVH Cloud OVH Cloud

size_t vs vector::size_type

24 réponses
Avatar
Michel Michaud
Il me semble que James a déjà démontré par des articles de la norme,
que vector<>::size_type ne peut pas être différent de size_t. Je me
trompe ?

Est-il alors « correct » d'écrire :

vector<TypeQuelconque> v;
...
for (size_t i= 0; i != v.size(); ++i)
...

Je trouve difficile d'introduire vector::size_type rapidement.
Surtout lorsque les vecteurs contiennent des types personnels :

for (vector<TypeQuelconque>::size_type i= 0; i != v.size(); ++i)

C'est terriblement lourd, il me semble. Au point où il apparaît
qu'un typedef (pour vector<TypeQuelconque>::size_type) serait très
utile. Mais est-ce raisonnable de faire un typedef pour chaque type
de vector (dans bien des programmes, il en faudrait un pour
vector<int>::size_type, un autre pour vector<double>::size_type,
etc.) si size_t fera l'affaire dans tous les cas ?

J'utilise actuellement int dans les premiers exemples que je donne
à mes élèves, mais le compilateur peut donner des avertissements,
justifiés, car il y a alors mélange signé/non signé dans les
comparaisons avec size() et il est facile d'imaginer que int n'est
pas suffisant pour toutes les tailles possibles des vecteurs (en
particulier, si int a 16 bits). J'aimerais donc faire mieux, mais
sans avoir à expliquer les types imbriqués, les typedef, etc.,
avant d'avoir expliqué l'utilisation simple des vecteurs ! Dois-
je revenir aux vecteurs de base à la C ? J'aimerais mieux pouvoir
simplement utiliser size_t !

L'emploi de valeurs non signées comme indice n'est pas sans
problème non plus, car il est assez fréquent qu'un indice serve
dans un calcul mettant en scène des valeurs signées. Là encore,
le compilateur peut donner des avertissements... Inversement on
peut vouloir calculer un indice à partir de valeurs signées,
mais qui aimera écrire

int indice= ...
...
v[static_cast<vector<TypeQuelconque>::size_type>(indice)]

au lieu de v[indice] !

N.B. BS utilise normalement int dans TC++PL... Je crois que je
vais lui écrire pour avoir son avis...


--
Michel Michaud mm@gdzid.com
http://www.gdzid.com
FAQ de fr.comp.lang.c++ :
http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ/

10 réponses

1 2 3
Avatar
kanze
"Michel Michaud" wrote in message
news:<rMxRb.15813$...

[...]
Dans une boucle for avec size_type, la déclaration de la variable de
contrôle prend en effet beaucoup de place et presque toute la place !

Dans la pratique James, tu écris quoi ?


size_t.

Dans la pratique, je vois mal une implémentation qui utilise d'autre
chose, même si c'est permis en théorie. Je suis même rassurer par la
poste de Gabriel, où il indique qu'il faut que le type soit non-signé.
(Et on sait qu'avec l'allocateur standard, les indices d'un vector ne
peut pas prendre des valeurs qui dépasse les limites d'un size_t.)

Mais en général, quand je me sers de std::vector, je me sers plutôt de
std::vector<>::iterator ou std::vector<>::const_iterator:-).

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16

Avatar
kanze
Gabriel Dos Reis wrote in message
news:...
writes:

| Gabriel Dos Reis wrote in message
| news:...
| > "Michel Michaud" writes:

| > | Il me semble que James a déjà démontré par des articles de la
| > | norme, que vector<>::size_type ne peut pas être différent de
| > | size_t. Je me trompe ?

| > Je ne me souviens pas. Je ne vois pas ce qui l'empêche d'être un
| > unsigned int, même si size_t est un unsigned long.

| Moi non plus. En fait, il y a deux questions : dans le cas général,
| il me semble clair -- rien n'empêche d'écrire un allocateur avec un
| size_type char, si cela t'enchante. En revanche, la norme exige bien
| que le size_type de l'allocateur standard soit size_t. Reste la
| question si le size_type de std::vector doit être identique au
| size_type de son allocateur. Logiquement, je m'attendrais que si,
| mais je ne suis pas sûr que la norme l'exige.

La norme ne l'exige pas. Tout ce qu'elle dit c'est que c'est un
unsigned


Type entier de la norme, j'espère. L'implémentation n'a pas le droit
d'inventer son propre type pour le faire (qui ne se convertira même pas
implicitement en size_t). Ou ?

qui peut représenter les valeurs positives de
vector<>::difference_type -- qui lui même mesure la distance algébrique
entre deux itérateurs sur un vecteur, et un vector<T*>::iterator n'est
pas obligé à être un T*. Si on veut ête minimaliste, on peut dire que
c'est un type entier qui peut représenter
vector<>::allocator_type::max_size() / sizeof (T).


Donc, pas de risque de débordement si je me sers d'un size_t (et
uniquement de l'allocator standard).

Dans la pratique, je ne sais pas s'il y a beaucoup d'implémentations
qui se compliquent la vie avec ce minimalisme.


Je crois que c'était entendu qu'on parlait de ce qui était permis, et
non seulement de ce qui se fait ou de qui risque de se faire.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16

Avatar
Samuel Krempp
le Wednesday 28 January 2004 01:53, écrivit :
Avant qu'il deviennent « mûrs », ils utilises « vector » -- sans
qualification.


qui est donc un type fourni par l'inclusion d'un en-tête maison si je
comprends bien.
ce type encapsule std::vector et fournit toute une interface similaire à
std::vector (à qques détails prêt dont le type de size() et des indices)
genre
template <typename T>
class vector {
std::vector<T> v_;
public :
int size() const;
T& operator[] (int i); range-check puis v_[i]
// resize(..) assign(..) et tout le reste ..
...
};

c'est ça ? ou bien le maximum de fonction est repris directment en héritant
de std::vector plutot qu'aggrégation ?

| Tu ne m'as pas dit explicitement de quel type tu déclares les
| indices (je suppose que c'est int...).

for (int i = 0; i < N; ++i)
v[i] = xxxx;


C'est ce que j'avais décidé de prendre l'habitude de faire au début, je
considèrais que je n'avais pas particulierement besoin d'un bit de plus
pour les tailles maximales et que je préferais éviter tant que possible de
me frotter à des problèmes de signé/non-signés, mais le fait que tout ce
qui existe utilise des non-signés m'a vite fait laisser de coté cette
philosophie, et j'ai malgré moi pris l'habitude de déclarer mes indices en
size_t, et de m'embêter à expliciter et vérifier les conversion
signé/non-signé.. (ou de les oublier carrément)

Est-ce que tu as une bonne solution pour permettre de garder le choix des
signés tout en pouvant s'adapter ensuite à des containers qui utilisent des
indices non-signé ?

#pragma bidule désactive le warning des comparaison signé/non-signé
for(int i=0; i<v.size(); ++i)

m'embête un peu, puisque le jour éventuel où le cas se présente d'un
v.size() pas représentable sur un int, la boucle ne finira pas et on ne
saura pas pquoi. (ou alors si v.size() est juste unsigned int, la boucle
finira comme il faut mais en passant par des i négatifs qui pourraient
poser problème dans le corps de la boucle)

J'ai envisagé d'adopter une habitude dans le genre de :
template<typename SizeType>
int sz2int(SizeType sz) {
int ret = static_cast<int> (sz);
assert(sz == ret); // promote & compare
assert(ret >= 0);
return ret;
}

...

for(int i=0; i<sz2int(v.size()); ++i)

mais je n'étais pas très sûr de ce que je devrais mettre dans sz2int.

Là j'ai réfléchi un peu avant de faire celui-ci, et il me semble que c'est
transparent si SizeType est en fait plus petit que int, que ça fait ce
qu'il faut quand SizeType est unsigned int ou unsigned long, et que dans
l'hypothèse où SizeType serait un type (ajouté) encore plus grand, ça
pourrait encore marcher pour peu que l'operator== fasse ce qu'il faut..

est ce que cette façon de faire est valable dans les cas où on décide de se
limiter au traitement de containers dont les indices tiennent dans un int
ou ya des trucs auxquels j'ai pas pensé ?

Il est cohérent avec ce qu'il écrit dans son bouquin :-)


ça nous dit pas comment il fait concretement pour adapter correctment
l'utilisation de la stdlib à son choix d'utiliser des indices signés, je
crois que les exmples du bouquin ne vont pas au delà de boucles que je
préfererais éviter, genre p94 :

void fp(char v[] , unsigned int size) {
for (int i=0; i<size; i++) use(v[i]) ;


--
Sam

Avatar
Gabriel Dos Reis
Samuel Krempp writes:

| le Wednesday 28 January 2004 01:53, écrivit :
| > Avant qu'il deviennent « mûrs », ils utilises « vector » -- sans
| > qualification.
|
| qui est donc un type fourni par l'inclusion d'un en-tête maison si je
| comprends bien.

Yep.

| ce type encapsule std::vector et fournit toute une interface similaire à
| std::vector (à qques détails prêt dont le type de size() et des indices)
| genre
| template <typename T>
| class vector {
| std::vector<T> v_;
| public :
| int size() const;
| T& operator[] (int i); range-check puis v_[i]
| // resize(..) assign(..) et tout le reste ..
| ...
| };
|
| c'est ça ? ou bien le maximum de fonction est repris directment en héritant
| de std::vector plutot qu'aggrégation ?

Yep.

template<class T, class A = std::allocator<T> >
struct vector : std::vector<T, A> {
// forwarding constructors
// define operator[] to do the "right thing"
};

| > | Tu ne m'as pas dit explicitement de quel type tu déclares les
| > | indices (je suppose que c'est int...).
| >
| > for (int i = 0; i < N; ++i)
| > v[i] = xxxx;
|
| C'est ce que j'avais décidé de prendre l'habitude de faire au début, je
| considèrais que je n'avais pas particulierement besoin d'un bit de plus
| pour les tailles maximales et que je préferais éviter tant que possible de
| me frotter à des problèmes de signé/non-signés, mais le fait que tout ce
| qui existe utilise des non-signés m'a vite fait laisser de coté cette
| philosophie, et j'ai malgré moi pris l'habitude de déclarer mes indices en
| size_t, et de m'embêter à expliciter et vérifier les conversion
| signé/non-signé.. (ou de les oublier carrément)

Oui, c'est une réel problème :-(

| Est-ce que tu as une bonne solution pour permettre de garder le choix des
| signés tout en pouvant s'adapter ensuite à des containers qui utilisent des
| indices non-signé ?
|
| #pragma bidule désactive le warning des comparaison signé/non-signé
| for(int i=0; i<v.size(); ++i)
|
| m'embête un peu, puisque le jour éventuel où le cas se présente d'un
| v.size() pas représentable sur un int, la boucle ne finira pas et on ne
| saura pas pquoi. (ou alors si v.size() est juste unsigned int, la boucle
| finira comme il faut mais en passant par des i négatifs qui pourraient
| poser problème dans le corps de la boucle)

Oui, tout ce qui commence par CPP est un non-starter.

| J'ai envisagé d'adopter une habitude dans le genre de :
| template<typename SizeType>
| int sz2int(SizeType sz) {
| int ret = static_cast<int> (sz);
| assert(sz == ret); // promote & compare
| assert(ret >= 0);
| return ret;
| }
|
| ...
|
| for(int i=0; i<sz2int(v.size()); ++i)
|
| mais je n'étais pas très sûr de ce que je devrais mettre dans sz2int.

Par défaut je lèverais un exception, à moins d'avoir des information
me disant qu'il vaut mieux utiliser assert() -- qui n'est pas toujours
sous contrôle.

| Là j'ai réfléchi un peu avant de faire celui-ci, et il me semble que c'est
| transparent si SizeType est en fait plus petit que int, que ça fait ce
| qu'il faut quand SizeType est unsigned int ou unsigned long, et que dans
| l'hypothèse où SizeType serait un type (ajouté) encore plus grand, ça
| pourrait encore marcher pour peu que l'operator== fasse ce qu'il faut..

Yep.

| est ce que cette façon de faire est valable dans les cas où on décide de se
| limiter au traitement de containers dont les indices tiennent dans un int
| ou ya des trucs auxquels j'ai pas pensé ?

je crois que oui.

| > Il est cohérent avec ce qu'il écrit dans son bouquin :-)
|
| ça nous dit pas comment il fait concretement pour adapter correctment
| l'utilisation de la stdlib à son choix d'utiliser des indices signés, je
| crois que les exmples du bouquin ne vont pas au delà de boucles que je
| préfererais éviter, genre p94 :
|
| void fp(char v[] , unsigned int size) {
| for (int i=0; i<size; i++) use(v[i]) ;

C'est vrai. Si tu regardes bien, il utilise les iterator là où cela
devient non trivial. En fait, si tu adhères à la religion que tes
fonctions ne devraient pas faire plus d'une quizaine de lignes, il est
fréquent que la plupart des structures de contrôle s'expriment en
terme d'itérateurs.

-- Gaby
Avatar
Gabriel Dos Reis
writes:

| > La norme ne l'exige pas. Tout ce qu'elle dit c'est que c'est un
| > unsigned
|
| Type entier de la norme, j'espère. L'implémentation n'a pas le droit
| d'inventer son propre type pour le faire (qui ne se convertira même pas
| implicitement en size_t). Ou ?

Dans la définition de C90 (qui est celle incluse dans C++), le
compilateur n'a pas le droit d'inventer ses propres types entiers.
Cependant, si jamais on revisait C++ pour adopter la définition C99,
cela pourrait poser un problème.

-- Gaby
Avatar
Michel Michaud
Dans news:, Gabriel Dos
for (int i = 0; i < N; ++i)
v[i] = xxxx;


Commentaire : je suis maintenant fortement d'accord avec
Koenig, surtout pour les débutants : pour les boucles de
0 à N-1, et autres qui parcourent toutes les données, la
condition devrait être i != N pour éviter que des erreurs
passent inaperçues jusqu'au « moment opportun » :-).

Ce n'est qu'un bonne petite habitude à prendre...

--
Michel Michaud
http://www.gdzid.com
FAQ de fr.comp.lang.c++ :
http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ/

Avatar
Samuel Krempp
le Thursday 29 January 2004 00:42, écrivit :

Yep.

template<class T, class A = std::allocator<T> >
struct vector : std::vector<T, A> {
// forwarding constructors
// define operator[] to do the "right thing"
};


ca apporte qque chose d'hériter plutot que d'aggreger ?

C'est vrai. Si tu regardes bien, il utilise les iterator là où cela
devient non trivial. En fait, si tu adhères à la religion que tes
fonctions ne devraient pas faire plus d'une quizaine de lignes, il est
fréquent que la plupart des structures de contrôle s'expriment en
terme d'itérateurs.


oui mais les itérateurs sont souvent plus long à taper d'un ordre de
magnitude..
alors j'attends les syntaxes d'autodéclaration avant d'envisager de
systématiquement écrire en itérateurs.. ;)

--
Sam

Avatar
Gabriel Dos Reis
Samuel Krempp writes:

| le Thursday 29 January 2004 00:42, écrivit :
|
| > Yep.
| >
| > template<class T, class A = std::allocator<T> >
| > struct vector : std::vector<T, A> {
| > // forwarding constructors
| > // define operator[] to do the "right thing"
| > };
|
| ca apporte qque chose d'hériter plutot que d'aggreger ?

Je poserais plutôt la question dans l'autre sens.

| > C'est vrai. Si tu regardes bien, il utilise les iterator là où cela
| > devient non trivial. En fait, si tu adhères à la religion que tes
| > fonctions ne devraient pas faire plus d'une quizaine de lignes, il est
| > fréquent que la plupart des structures de contrôle s'expriment en
| > terme d'itérateurs.
|
| oui mais les itérateurs sont souvent plus long à taper d'un ordre de
| magnitude..

Encore faut-il qu'il y ait besoin de les taper ;-)

-- Gaby
Avatar
Samuel Krempp
le Thursday 29 January 2004 12:52, écrivit :

Samuel Krempp writes:

| le Thursday 29 January 2004 00:42, écrivit :
|
| > Yep.
| >
| > template<class T, class A = std::allocator<T> >
| > struct vector : std::vector<T, A> {
| > // forwarding constructors
| > // define operator[] to do the "right thing"
| > };
|
| ca apporte qque chose d'hériter plutot que d'aggreger ?

Je poserais plutôt la question dans l'autre sens.


alors je reformule : quelle différence y-a-t-il entre aggreger ou dériver
std::vector pour cette classe ?

comme j'ai un vague souvenir d'une devise recommandant de préferer
l'aggrégation à la dérivation à moins qu'il y ait vraiment une raison de
préferer dériver, je pensais donc qu'il y a en l'occurence un avantage à la
dérivation, d'où la formulation de ma question.
Ce que je ne sais pas dans le fond, c'est si on peut être sûr de ne courir
aucun risque en dérivant une classe qui n'offre aucune garantie à ses
héritiers éventuels. Je n'en vois aucun, mais je n'ai jamais cherché à
connaître toutes les conséquences imaginables de dériver une classe..
Sinon j'imagine que dériver apporte la conversion vers std::vector comme on
peut la vouloir.

Ta réponse appelle une autre question car elle sous-entends que si on a le
choix entre dériver ou aggréger sans différence de sémantique les 2 choix
sont aussi bons l'un que l'autre. La devise que j'évoque ci-dessus n'a donc
rien de fondé ?

--
Sam

Avatar
Gabriel Dos Reis
Samuel Krempp writes:

| le Thursday 29 January 2004 12:52, écrivit :
|
| > Samuel Krempp writes:
| >
| > | le Thursday 29 January 2004 00:42, écrivit :
| > |
| > | > Yep.
| > | >
| > | > template<class T, class A = std::allocator<T> >
| > | > struct vector : std::vector<T, A> {
| > | > // forwarding constructors
| > | > // define operator[] to do the "right thing"
| > | > };
| > |
| > | ca apporte qque chose d'hériter plutot que d'aggreger ?
| >
| > Je poserais plutôt la question dans l'autre sens.
|
| alors je reformule : quelle différence y-a-t-il entre aggreger ou dériver
| std::vector pour cette classe ?

Dans le contexte actuel, il y a moins de boulot à faire avec la
dérivation et personne ne risque rien.

| comme j'ai un vague souvenir d'une devise recommandant de préferer
| l'aggrégation à la dérivation à moins qu'il y ait vraiment une raison de
| préferer dériver, je pensais donc qu'il y a en l'occurence un avantage à la
| dérivation, d'où la formulation de ma question.

Ces discussions ont toujours un contexte précis. Si on oublie le
context, on risque de pédaler dans le vide. En particulier, ces
discussions supposent qu'il y a connaissance de l'exitence de la
classe de base et que certains feraient des bêtises avec. Mais dans le
contexte actuel, à part le prof, personne ne sait l'existance de la
classe de base. Il n'y a donc aucun risque.

| Ce que je ne sais pas dans le fond, c'est si on peut être sûr de ne courir
| aucun risque en dérivant une classe qui n'offre aucune garantie à ses
| héritiers éventuels.

Je ne comprends pas cette phrase. Si j'hérite d'une classe, j'hérite
de toutes ces fonctions membres. Surtout n'oublie pas le contexte
spécifique sous la main.

| Je n'en vois aucun, mais je n'ai jamais cherché à
| connaître toutes les conséquences imaginables de dériver une classe..

Est-ce important dans le cas spécifique sous la main ?

| Sinon j'imagine que dériver apporte la conversion vers std::vector comme on
| peut la vouloir.

Pourquoi faire ? Les élèves ne savent pas que std::vector existe ou
que vector dérive de std::vector. On leur demande d'include "112.h"
et d'utiliser "vector".

| Ta réponse appelle une autre question car elle sous-entends que si on a le
| choix entre dériver ou aggréger sans différence de sémantique les 2 choix
| sont aussi bons l'un que l'autre. La devise que j'évoque ci-dessus n'a donc
| rien de fondé ?

Je ne dis pas ça. Je dis simplement que ce que tu ne sais pas ne te
tuera pas. En particulier, si tu ne sais pas que la classe de base
existe et qu'on en a dérivé pour te donner quelque chose, tu ne
risques pas de faire des bêtises avec.
Quelle est la dernière fois que tu as fais des bêtises avec
std::_Vector_base et std::vector à cause de la dérivation ?

-- Gaby
1 2 3