4
votes

Puis-je éviter de copier lors de l'initialisation d'un std :: initializer_list sans utiliser de pointeurs bruts?

Disons que j'ai plusieurs objets déclarés localement sur lesquels je veux itérer en utilisant la syntaxe basée sur la plage. Cela semble bien fonctionner, cependant, il semble que pour mettre les objets locaux dans la liste d'initialisation, une copie est effectuée. C'est une mauvaise nouvelle pour des objets comme std :: shared_ptr pour lesquels (si je comprends bien) l'incrémentation du nombre de références est une opération atomique. Le seul moyen que je pense que cela puisse être évité est d'utiliser des pointeurs bruts.

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptrInt1 = std::make_shared<int>(1);
    std::shared_ptr<int> ptrInt2 = std::make_shared<int>(2);
    /* in this loop, ptrInt1 and ptrInt2 are copied before they are binded
       to ptrInt, this is ugly since the reference counter needs to temporarily
       increased */
    for(const std::shared_ptr<int>& ptrInt : {ptrInt1, ptrInt2}) {
        std::cerr << *ptrInt << std::endl;
    }
    /* this solution works, but it feels somewhat ugly having to convert my smart
       pointers to raw pointers to avoid the copying, perhaps there is a better
       solution ?? */
    for(const int* rawPtrInt : {ptrInt1.get(), ptrInt2.get()}) {
        std::cerr << *rawPtrInt << std::endl;
    }
    return 0;
}

Existe-t-il un moyen d'itérer sur un groupe d'objets déclarés localement sans les copier ou recourir à des pointeurs bruts?


9 commentaires

Cela peut être "mauvais" pour par exemple std :: unique_ptr qui ne peut pas être copié, mais je ne vois pas le problème avec std :: shared_ptr . Oui, une copie sera faite, le compteur de référence augmentera, mais alors la copie sera détruite et le compteur de référence sera à nouveau diminué. Pourquoi c'est un problème?


Je suis un peu confus quant à la raison pour laquelle l'incrémentation du nombre de références étant atomique est problématique. Je veux dire que je comprends que vous voulez éviter la copie, mais cela s'applique en général non seulement aux pointeurs intelligents dont l'incrément de comptage de référence est atomique, ou est-ce que je rate quelque chose?


@Someprogrammerdude, c'est un gros problème de performances dans certains cas, car cette augmentation est atomique et, dans de nombreux cas, rend les optimisations du compilateur indisponibles.


@SergeyA exactement


bien c'est ce que j'ai manqué;)


Créer une fonction d'assistance qui renvoie un tableau de wrappers de référence à vos objets?


Au lieu d'utiliser un tableau temporaire, que diriez-vous de faire l'inverse? C'est à dire. vous mettez vos shared_ptrs dans un tableau / vecteur puis définissez ptrInt1 comme référence.


Ensuite, nous arrivons à la discussion sur d'éventuelles optimisations prématurées ... S'agit-il d'un goulot d'étranglement mesuré dans votre programme? À moins que ce ne soit l'un des deux principaux goulots d'étranglement (ou peut-être trois), ma suggestion est de ne pas déranger. Tout d'abord, concentrez-vous sur l'écriture d'un code bon, propre, facilement compréhensible et maintenable qui n'a pas besoin de commentaires en raison de l'optimisation-obfuscation. Si l '«efficacité» mesurée ne correspond pas aux exigences (et rappelez-vous que «assez bien» est souvent assez bon ), établissez un profil et mesurez pour trouver les goulots d'étranglement sur lesquels concentrer vos efforts.


@Someprogrammerdude n'est pas du tout d'accord. Par exemple, dans mon secteur d'activité, c'est une approche tout simplement inacceptable, et le code doit être optimisé à partir de zéro. Cela pourrait être «une approche», mais ne pas trop généraliser.


3 Réponses :


2
votes

Malheureusement, std :: initializer_list est mal adapté pour cette tâche. Depuis

Le tableau sous-jacent est un tableau temporaire de type const T [N], dans lequel chaque élément est initialisé par copie ...

( https://en.cppreference.com/w/cpp/utility/ initializer_list ), il va effectuer des copies, et le compilateur ne les éloigne pas.

Pour cette tâche, je ferais autre chose. Créez probablement une classe de modèle variadique au moment de la compilation basée sur des pointeurs vers des objets sous-jacents. Faites-moi savoir si vous souhaitez du code.


0 commentaires

5
votes

10 commentaires

ou {std :: move (ptrInt1), std :: move (ptrInt2)} mais pas bon comme std :: cref


@JeJo move serait destructeur et cref est redondant.


@NathanOliver est d'accord avec std :: move , mais std :: cref ? serait le bon choix que std :: ref comme const std :: shared_ptr & ptrInt ?


@JeJo Voulez-vous dire utiliser quelque chose comme pour (auto & ptrInt: {std :: cref (ptrInt1), std :: cref (ptrInt2)})


dans votre exemple de code au lieu de std :: ref : const std :: shared_ptr & ptrInt: {std :: cref (ptrInt1), std :: cref (ptrInt2)} < / code>, comme dans @lubgr a utilisé la réponse .


@JeJo Ok. C'est redondant. const std :: shared_ptr & ptrInt vous donne déjà une référence à const donc ajouter le c à ref est juste de taper vous ne ' t faire.


auto ptrIntRef: {std :: cref (ptrInt1), std :: cref (ptrInt2)} :-)


@ Jarod42 Malheureusement, cela ne se compilera pas car le type de ptrIntRef devient std :: reference_wrapper > : coliru.stacked-crooked.com/a/d9d2610005d6f19a


Pas idéal Démo , votre version semble tellement meilleure.


@ Jarod42 Ce qui serait vraiment bien, c'est que l'opérateur surchargé opérateur << et opérateur >> et le transmette simplement au type correct. Vraiment, ils devraient avoir cela pour tous les opérateurs. J'écrirai peut-être un article pour ça.



3
votes

Voici un petit modèle de fonction qui étend la réponse de @ NathanOliver et réduit une partie de la saisie.

for(const std::shared_ptr<int>& ptrInt : crefRange(ptrInt1, ptrInt2))
    std::cerr << *ptrInt << std::endl;


6 commentaires

C'est en fait un peu dangereux tel quel. auto crefRange (const T & ... args) acceptera les temporaires afin que vous construisiez un tableau de reference_wrapper s pendant. Je pense que vous pouvez le résoudre en utilisant template auto crefRange (T & ... args) puis en ajoutant template auto crefRange (T && ... args) = supprimer; .


@NathanOliver Bon point, certes, je n'y ai pas pensé. Les surcharges rvalue du modèle std :: cref sont explicitement delete d, cependant - le fardeau de gérer ce cas est donc déjà couvert (et de passer les temporaires à crefRange ne compile pas).


Malheureusement, dans crefRange args se trouve une lvalue car elle a un nom et n'est donc pas couverte. voir: coliru.stacked-crooked.com/a/9fdf8924ff4ee548 (ça marche mais ça n'est pas obligé)


@NathanOliver Vous avez raison, args est une lvalue ... mh - pourquoi me donne-t-il copier l'élément de tableau [...] invoque le constructeur supprimé sur std :: cref (args) ... puis, lors de l'appel en tant que crefRange (std :: make_unique (42)); ?


Ah, je l'ai manqué. return std :: array dit make an array du premier élément passé donc dans le premier cas c'était shared_ptr et dans mon cas c'était int. Ce n'était pas un tableau de wrappers de référence. Il essaie donc de copier unique_ptr dans le tableau. Vous avez besoin de quelque chose comme return std :: array ())), sizeof ... (T)> {{std :: cref (args). ..}}; pour obtenir un tableau de références et ensuite ce segment des fautes: coliru .stacked-crooked.com / a / c2454421e2f1a5cf


@NathanOliver Je vois, merci pour l'indice. Je dois réparer cela.