web-dev-qa-db-fra.com

Faut-il passer un shared_ptr par référence ou par valeur?

Quand une fonction prend un shared_ptr (de boost ou STL C++ 11), le passez-vous:

  • par référence de const: void foo(const shared_ptr<T>& p)

  • ou par valeur: void foo(shared_ptr<T> p)?

Je préférerais la première méthode parce que je soupçonne que ce serait plus rapide. Mais est-ce vraiment utile ou y at-il des problèmes supplémentaires?

Pourriez-vous s'il vous plaît donner les raisons de votre choix ou, le cas échéant, pourquoi vous pensez que cela n'a pas d'importance.

240
Danvil

Scott, Andrei et Herb ont discuté de cette question et y ont répondu au cours de la session demandez-nous quelque chose à la session C++ et au-delà de 2011 . Regarder à partir de 4min 34s sur shared_ptr performances et exactitude .

En bref, il n’ya aucune raison de passer par valeur , sauf si l’objectif est de partager la propriété d’un objet (par exemple entre différentes structures de données ou entre différentes structures de données). fils).

À moins que vous ne puissiez le déplacer-l’optimiser comme expliqué par Scott Meyers dans la vidéo de discussion liée ci-dessus, mais cela est lié à la version actuelle de C++ que vous pouvez utiliser.

Une mise à jour majeure de cette discussion a eu lieu au cours de GoingNative 2012 de la conférence Panel interactif: Demandez-nous n'importe quoi! qui mérite d'être surveillé, en particulier de 22:5 .

203
mloskot

Voici prise de Herb Sutter

Ligne directrice: ne transmettez pas un pointeur intelligent en tant que paramètre de fonction, sauf si vous souhaitez utiliser ou manipuler le pointeur intelligent lui-même, par exemple pour le partager ou le transférer.

Recommandation: Indiquez qu'une fonction va stocker et partager la propriété d'un objet tas à l'aide d'un paramètre by_valeur shared_ptr.

Ligne directrice: Utilisez un paramètre shared_ptr & non-const uniquement pour modifier le shared_ptr. Utilisez un const shared_ptr & en tant que paramètre uniquement si vous ne savez pas si vous allez en prendre une copie et en partager la propriété; sinon, utilisez widget * à la place (ou si non nullable, un widget &).

78
acel

Personnellement, j'utiliserais une référence const. Il n'est pas nécessaire d'incrémenter le compte de références pour le décrémenter à nouveau pour un appel de fonction.

60
Evan Teran

Passer par const référence, c'est plus rapide. Si vous avez besoin de le stocker, disons dans un conteneur, la réf. Le compte sera automatiquement incrémenté par l'opération de copie.

34
Nikolai Fetissov

J'ai couru le code ci-dessous, une fois avec foo en prenant le shared_ptr de const& et encore avec foo en prenant le shared_ptr par valeur.

void foo(const std::shared_ptr<int>& p)
{
    static int x = 0;
    *p = ++x;
}

int main()
{
    auto p = std::make_shared<int>();
    auto start = clock();
    for (int i = 0; i < 10000000; ++i)
    {
        foo(p);
    }    
    std::cout << "Took " << clock() - start << " ms" << std::endl;
}

Utilisation de VS2015, version x86, sur mon processeur Intel Core 2 quad (2,4 GHz)

const shared_ptr&     - 10ms  
shared_ptr            - 281ms 

La version copie par valeur était un ordre de grandeur plus lent.
Si vous appelez une fonction de façon synchrone à partir du thread actuel, préférez la version const&.

20
tcb

Depuis C++ 11, vous devriez le prendre valeur sur const plus souvent que vous ne le pensez.

Si vous prenez le std :: shared_ptr (plutôt que le type sous-jacent T), vous le faites parce que vous voulez faire quelque chose avec.

Si vous voulez le copier quelque part, il est plus logique de le prendre par copie et de std :: move en interne, plutôt que de le prendre par const & puis de le copier ultérieurement. En effet, vous laissez à l'appelant l'option lui-même std :: move le shared_ptr lors de l'appel de votre fonction, vous évitant ainsi un ensemble d'opérations d'incrémentation et de décrémentation. Ou pas. En d’autres termes, l’appelant de la fonction peut décider s’il a ou non besoin de std :: shared_ptr après avoir appelé la fonction, et selon qu’il se déplace ou non. Ce n'est pas réalisable si vous passez par const &, et il est donc préférable de le prendre en valeur.

Bien sûr, si l'appelant à la fois a besoin de son shared_ptr plus longtemps (vous ne pouvez donc pas std :: move le) et que vous ne voulez pas créer une copie simple dans la fonction (disons que vous voulez un pointeur faible, ou que vous voulez parfois seulement pour le copier, en fonction de certaines conditions), alors un const & pourrait toujours être préférable.

Par exemple, vous devriez faire

void enqueue(std::shared<T> t) m_internal_queue.enqueue(std::move(t));

plus de

void enqueue(std::shared<T> const& t) m_internal_queue.enqueue(t);

Parce que dans ce cas, vous créez toujours une copie en interne

13
Cookie

Ne connaissant pas le coût en temps de l'opération de copie shared_copy où l'incrément et le décrément atomique sont en place, j'ai souffert d'un problème d'utilisation du processeur beaucoup plus important. Je ne m'étais jamais attendu à ce que l'incrémentation atomique et le décrémentation coûtent si cher.

Après le résultat de mon test, l'incrément et la décrémentation atomique d'int32 prennent 2 à 40 fois plus longtemps que l'incrémentation et la décrémentation non atomiques. Je l'ai eu sur 3GHz Core i7 avec Windows 8.1. Le premier résultat apparaît en l'absence de conflit, le dernier en cas de risque élevé de conflit. Je garde à l'esprit que les opérations atomiques sont enfin un verrou matériel. Lock est verrouillé. Mauvaises performances en cas de conflit.

Expérimentant cela, j'utilise toujours byref (const shared_ptr &) que byval (shared_ptr).

1
Hyunjik Bae