53 votes

std::vector, construction par défaut, C++11 et changements de rupture

Je me suis heurté aujourd'hui à un problème assez subtil sur lequel j'aimerais avoir votre avis.

Considérons la classe d'idiomes à corps partagé suivante :

struct S
{
    S() : p_impl(new impl) {}
private:
    struct impl;
    boost::shared_ptr<impl> p_impl;
};

Le plaisir apparaît lorsque vous essayez de les transformer en vecteurs de la manière suivante :

std::vector<S> v(42);

Désormais, avec MSVC 8 au moins, tous les éléments de la section v partagent le même impl membre. En fait, ce qui cause ce problème est le vector constructeur :

template <typename T, typename A = ...>
class vector
{
    vector(size_t n, const T& x = T(), const A& a = A());
    ...
};

Dans les coulisses, un seul S est construit par défaut, l'objet n éléments de la vector sont copiés à partir de celui-ci.

Maintenant, avec C++11, il y a des références rvalue. Donc ça ne peut pas fonctionner comme ça. Si une vector est construit comme suit

std::vector<S> v(42);

alors il est fort probable que les implémentations choisiront de construire par défaut l'option n à l'intérieur du vecteur, puisque la construction par copie peut ne pas être disponible. Il s'agirait d'un changement de rupture dans ce cas.

Ma question est la suivante :

  1. La norme C++03 impose-t-elle que std::vector doit avoir un constructeur défini comme ci-dessus, c'est-à-dire avec un argument par défaut ? En particulier, y a-t-il une garantie que les entrées de l'objet vectoriel seront copiées au lieu d'être construites par défaut ?
  2. Que dit la norme C++11 sur ce même point ?
  3. Je vois là la possibilité d'un changement de rupture entre C++03 et C+11. Ce problème a-t-il été étudié ? Résolu ?

PS : S'il vous plaît pas de commentaires sur le constructeur par défaut de la classe S ci-dessus. C'était ça ou la mise en œuvre d'une forme de construction paresseuse.

46voto

James McNellis Points 193607

La norme C++03 exige-t-elle que std::vector doit avoir un constructeur défini comme ci-dessus, c'est-à-dire avec un argument par défaut ? En particulier, y a-t-il une garantie que les entrées de l'objet vectoriel soient copiées au lieu d'être construites par défaut ?

Oui, le comportement spécifié est que x est copié n de sorte que le conteneur soit initialisé pour contenir avec n qui sont tous des copies de x .


Que dit la norme C++11 sur ce même point ?

En C++11, ce constructeur a été transformé en deux constructeurs.

vector(size_type n, const T& x, const Allocator& = Allocator()); // (1)
explicit vector(size_type n);                                    // (2)

À l'exception du fait qu'il n'a plus d'argument par défaut pour le second paramètre, (1) fonctionne de la même manière qu'en C++03 : x est copié n temps.

Au lieu de l'argument par défaut pour x , (2) a été ajouté. Ce constructeur initialise la valeur n éléments dans le conteneur. Aucune copie n'est effectuée.

Si vous avez besoin de l'ancien comportement, vous pouvez vous assurer que (1) est appelé en fournissant un second argument à l'invocation du constructeur :

std::vector<S> v(42, S());

Je vois là la possibilité d'un changement de rupture entre C++03 et C++11. Je vois là la possibilité d'un changement de rupture entre C++03 et C++11. Ce problème a-t-il été étudié ? Résolu ?

Oui, comme le montre votre exemple, il s'agit bien d'un changement de rupture.

Comme je ne suis pas membre du comité de normalisation C++ (et que je n'ai pas prêté une attention particulière aux articles relatifs aux bibliothèques dans les mailings), je ne sais pas dans quelle mesure ce changement de rupture a été discuté.

-3voto

Alexey Sergeev Points 11

Je pense que la solution pour le cas d'utilisation que vous avez décrit n'est pas optimale ni complète, c'est pourquoi vous avez eu des problèmes de mise à niveau vers C++11.

Le C++ se soucie toujours de la sémantique et lorsque vous écrivez un programme en C++, vous avez intérêt à comprendre votre sémantique. Ainsi, dans votre cas, vous souhaitez créer N objets, mais tant que vous ne les modifiez pas, vous souhaitez qu'ils partagent la même mémoire pour des raisons d'optimisation. Bonne idée, mais comment faire ? 1) copier le constructeur. 2) implémentation statique + constructeur de copie. Avez-vous envisagé les deux solutions ?

Considérons que vous avez besoin de M vecteurs de N objets, combien de fois la mémoire partagée sera-t-elle allouée si vous choisissez le 1er scénario ? C'est M, mais pourquoi devons-nous allouer la mémoire M fois si nous voulons créer des vecteurs contenant MxN objets ?

L'implémentation correcte ici est donc de pointer vers la mémoire statique par défaut, et d'allouer de la mémoire uniquement si l'objet est modifié. Dans un tel cas, l'allocation de M vecteurs de N objets vous donnera... 1 allocation de mémoire 'partagée'.

Dans votre cas, vous avez violé la sémantique correcte en abusant du constructeur de copie, qui est : 1) pas évident 2) pas optimal et maintenant vous devez payer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X