2010-07-04 8 views
7

Lorsque je regardais les fonctions membres des conteneurs STL, une idée étrange m'est venue à l'esprit. Pourquoi des fonctions comme std::vector<T>::push_back(T) n'ont-elles pas une valeur de retour (optionnelle) (itérateur ou même une référence à l'objet ajouté)? Je connais std::string fonctions comme insert et erase retour itérateurs, mais c'est pour des raisons évidentes. Je pense qu'il serait souvent enregistrer une deuxième ligne de code qui suit souvent ces appels de fonction.Valeurs de retour de la fonction de conteneur STL

Je suis sûr que les concepteurs de C++ ont une très bonne raison, s'il vous plaît me éclairer :)

MISE À JOUR: Je suis notamment un exemple de code dans le monde réel ici où il pourrait réduire la longueur de code:

if(m_token != "{") 
{ 
    m_targets.push_back(unique_ptr<Target>(new Dough(m_token))); 
    return new InnerState(*(m_targets.back()), this); 
} 

pourrait être réduite à

if(m_token != "{") 
    return new InnerState(*(m_targets.push_back(unique_ptr<Target>(new Dough(m_token)))), this); 

Si je suppose std::list::push_back renvoie une référence à l'élément ajouté. Le code est un peu lourd, mais c'est principalement (deux ensembles de parenthèses) dû au constructeur de unique_ptr et le déréférencement. Peut-être pour plus de clarté une version sans pointeurs:

if(m_token != "{") 
{ 
    m_targets.push_back(Dough(m_token)); 
    return new InnerState(m_targets.back(), this); 
} 

contre

if(m_token != "{") 
    return new InnerState(m_targets.push_back(Dough(m_token)), this); 

Répondre

5

Le retour de l'élément ajouté ou du conteneur dans les fonctions membres du conteneur n'est pas possible dans un moyen sûr. Les conteneurs STL fournissent principalement le "strong guarantee". Le retour de l'élément manipulé ou du conteneur rendrait impossible une forte garantie (il ne fournirait que la «garantie de base»). La raison derrière ceci est que renvoyer quelque chose pourrait éventuellement appeler un constructeur de copie, ce qui peut générer une exception. Mais la fonction déjà sortie, de sorte qu'il a rempli sa tâche principale avec succès, mais a encore jeté une exception, ce qui est une violation de la forte garantie. Vous pensez peut-être: "Eh bien, alors revenons par référence!", Alors que cela semble être une bonne solution, ce n'est pas parfaitement sûr non plus. Considérons l'exemple suivant:

MyClass bar = myvector.push_back(functionReturningMyClass()); // imagine push_back returns MyClass& 

encore, si l'opérateur-affectation de copie lancers francs, nous ne savons pas si push_back succeded ou non, violant ainsi indirectement la forte garantie. Même si ce n'est pas une violation directe. Bien sûr, utiliser MyClass& bar = //... à la place réglerait ce problème, mais il serait très incommode qu'un conteneur puisse entrer dans un état indéterminé, simplement parce que quelqu'un a oublié un &.

Un reasoning assez similaire est derrière le fait que std::stack::pop() ne renvoie pas la valeur sauté. Au lieu de cela, top() renvoie la valeur la plus élevée de manière sûre. après l'appel de top, même si un constructeur de copie ou un constructeur d'assignation de copie est lancé, vous savez toujours que la pile est inchangée.

EDIT: Je crois de retourner un itérateur pour l'élément nouvellement ajouté doit être parfaitement sûr, si la copie constructeur du type iterator fournit la garantie sans lancer (et je sais de fait tous les).

+0

Wow. Bonne info, merci! Reste à me demander pourquoi ils n'ont pas retourné un itérateur bien sûr :). Peut-être parce que vous pourriez alors 'MyClass bar = * (myvector.push_back (functionReturningMyClass()))' et probablement avoir le même problème que pour retourner une référence (ou pas?). – rubenvb

+0

Je suppose, la raison était des problèmes de performance, puisque cette chose nécessiterait la construction d'un itérateur, et la copie de cet itérateur, et c'est tout à fait un surcoût, si la valeur de retour n'est pas utilisée. – smerlin

+0

aucun compilateur décent n'optimiserait-il une telle chose? Probablement pas ce que le comité de normalisation regardait :) – rubenvb

-2

Je ne sais pas qu'ils avaient une très bonne raison, mais cette fonction est déjà assez lent.

+4

Comment ça, "lent"? et quelle différence ferait une valeur de retour? –

+0

étant donné que la valeur de retour est principalement renvoyée dans un registre, il y a une chance qu'elle renvoie déjà un pointeur vers l'élément inséré. Et cela ne ferait pas beaucoup de différence puisque le vecteur :: back() est constant donc un itérateur (ou pointeur) doit quand même être sauvegardé par le vecteur. – flownt

+3

Lent comparé à quoi? – GManNickG

5

Question intéressante. La valeur de retour évidente serait le vecteur (ou autre) que l'opération a lieu, donc vous pouvez alors écrire du code comme:

if (v.push_back(42).size() > n) { 
    // do something 
} 

Personnellement, je n'aime pas ce style, mais je ne peux pas penser à une bonne raison de ne pas le soutenir.

+0

Je pensais plus dans les lignes de 'element = v.push_back (i); element.somefunction (someVariable); ', mais le vôtre fonctionnerait aussi bien sûr. – rubenvb

0

Je pense que cela a à voir avec le concept d'une valeur de retour: la valeur de retour est là non pas pour votre commodité mais pour un résultat conceptuel du 'calcul' ils pensaient apparemment que push_back n'a aucun résultat conceptuel.

3

Parce qu'il y a .back() qui va le retourner instantanément pour vous? Conceptuellement, les concepteurs C++ n'implémenteront pas dans une fonction membre tout ce qui serait difficile ou impossible à implémenter dans une interface publique. Appeler .back() est assez simple et facile. Pour un itérateur, vous pouvez faire (fin - 1) ou juste auto it = end; it--;

Le comité de normalisation rend possible le nouveau code et simplifie massivement le code très utilisé. Des trucs comme ça ne sont pas sur la liste des choses à faire.

+0

Précision: assurez-vous qu'il y a un élément dans le conteneur avant d'appeler '--' sur' end'. Sinon, c'est un comportement indéfini. –

+0

C'est la même restriction que '.back()' a, et une postcondition de push_back() qui suit (c'est-à-dire ne pas lancer) – MSalters

0

Je ne suis pas sûr, mais je pense que l'une des raisons pour lesquelles les std::string membres modificatrices retournent un iterator est donc que le programmeur pourrait obtenir un non-const-iterator au std::string après une opération mutationniste sans nécessiter une seconde " fuite". L'interface std::basic_string a été conçue pour prendre en charge un modèle appelé copy-on-write, ce qui signifie que toute opération de mutation n'affecte pas les données d'origine, mais une copie.Par exemple, si vous avez eu la chaîne "abcde" et remplacé 'a' par 'z' pour obtenir "zbcde", les données de la chaîne résultante peuvent occuper un emplacement différent dans le tas que les données de la chaîne d'origine.

Si vous obtenez un non-const iterator d'un std::string, puis une mise en œuvre de la chaîne de COW doit faire une copie (également appelée « fuite de l'original »). Dans le cas contraire, le programme peut changer les données sous-jacentes (et contraire à la lecture seule invariant) with:

char& c0 = *str.begin(); 
c0 = 'z'; 

Mais, après une opération de mutation de chaîne, l'objet chaîne résultante a déjà la propriété exclusive des données, de sorte que la chaîne l'implémentation n'a pas besoin de fuir ses données une seconde fois pour faire un non-const-itérateur.

Un std::vector est différent car il ne prend pas en charge la sémantique de copie à l'écriture.

Remarque: J'ai reçu le terme fuite de l'implémentation libstdC++ de std::basic_string. En outre, "fuite des données" ne signifie pas que la mise en œuvre leaks memory.

EDIT: Voici la libstdC++ définition de std::basic_string<CharT, Traits, Alloc>::begin() pour référence:

iterator 
begin() 
{ 
    _M_leak(); 
    return iterator(_M_data()); 
} 
2
v.insert(v.end(),x); 

équivaudrait à push_back au retour d'un itérateur. Pourquoi push_back lui-même ne renvoie pas un itérateur est au-delà de moi.

0

Peut-être parce que ce n'était pas "nécessaire"?

erase() et insert() ont pas d'autre moyen que de retourner un itérateur pour permettre la poursuite d'une boucle, il a été appelé.

je ne vois pas une bonne raison de soutenir la même logique avec push_back().

Mais bien sûr, il serait merveilleux de faire des expressions plus cryptiques. (Je ne vois pas d'amélioration dans votre exemple, cela semble être un bon moyen de ralentir vos collègues lors de la lecture de votre code ...)

Questions connexes