57 votes

Quand la référence de const est-elle meilleure que la valeur de passage par la valeur en c ++11

J'ai quelques pré-c++11 code dans lequel j'utilise const références à passer de grands paramètres, comme l' vector's beaucoup. En voici un exemple:

int hd(const vector<int>& a) {
   return a[0];
}

J'ai entendu dire qu'avec c++11 caractéristiques, vous pouvez passer l' vector , en valeur, comme suit, sans impact négatif sur les performances.

int hd(vector<int> a) {
   return a[0];
}

Par exemple, cette réponse dit

C++11 de la sémantique de déplacement, faire passer et retourner en valeur beaucoup plus attractif, même pour des objets complexes.

Est-il vrai que les deux options sont les mêmes en terme de performance?

Si oui, quand est-utilisation de const référence comme dans l'option 1 de mieux que l'option 2? (c'est à dire pourquoi avons-nous encore besoin d'utiliser const références en c++11).

Une raison pour laquelle je demande, c'est que const références compliquer déduction des paramètres du modèle, et il serait beaucoup plus facile à utiliser passer par valeur, que, si c'est la même chose avec const référence en terme de performance.

Merci,

70voto

Rapptz Points 10135

La règle générale de pouce pour le passage par valeur est lorsque vous faites une copie de toute façon. C'est-à-dire que plutôt que de faire ceci:

void f(const std::vector<int>& x) {
    std::vector<int> y(x);
    // stuff
}

lorsque vous d'abord passer un const-ref et puis le copier, vous devriez faire ceci à la place:

void f(std::vector<int> x) {
    // work with x instead
}

Cela a été partiellement vrai en C++03, et est devenu de plus en plus utile sémantique de déplacement, la copie peut être remplacé par un déplacement dans le col-en-val cas lorsque la fonction est appelée avec une rvalue.

Sinon, quand tout ce que vous voulez faire est de lire les données, en passant par const de référence est toujours le préféré, de manière efficace.

11voto

cubuspl42 Points 1293

Il y a une grande différence. Vous obtiendrez une copie de l' vectors'tableau interne, sauf si elle a été sur le point de mourir.

int hd(vector<int> a) {
   //...
}
hd(func_returning_vector()); // internal array is "stolen" (move constructor is called)
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
hd(v); // internal array is copied (copy constructor is called)

C++11 et l'introduction de références rvalue changé les règles sur le renvoi des objets comme des vecteurs - maintenant, vous pouvez le faire (sans se soucier d'une garantie de copie). Pas de règles de base au sujet de prendre comme argument changé, cependant, vous devez toujours prendre par const référence, sauf si vous avez réellement besoin d'une vraie copie par valeur.

8voto

kfsone Points 7375

C++11 de la sémantique de déplacement, faire passer et retourner en valeur beaucoup plus attractif, même pour des objets complexes.

L'exemple que vous donnez, cependant, est un exemple de passage par valeur

int hd(vector<int> a) {

C++11 n'a pas d'impact sur ce point.

Même si vous aviez correctement déclarée " hd " pour prendre une rvalue

int hd(vector<int>&& a) {

il peut être moins cher que de passer par valeur mais en effectuant un déplacement réel peut être plus cher qu'un simple passage par référence. Un nouveau vector<int> doit être construit et il doit prendre possession du contenu de a. Nous n'avons pas les vieux surcharge d'avoir à allouer un nouveau tableau d'éléments et copiez les valeurs, mais nous avons encore besoin de transférer les données des champs de vector.

Plus important encore, dans le cas d'un déménagement, a serait détruite dans ce processus:

std::vector<int> x;
x.push(1);
int n = hd(std::move(x));
std::cout << x.size() << '\n'; // UB

C'est le point de l'ensemble d'un déplacement de l'opérateur.

Considérez les points suivants exemple complet:

struct Str {
    char* m_ptr;
    Str() : m_ptr(nullptr) {}
    Str(const char* ptr) : m_ptr(strdup(ptr)) {}
    Str(const Str& rhs) : m_ptr(strdup(rhs.m_ptr)) {}
    Str(Str&& rhs) {
      if (&rhs != this) {
        m_ptr = rhs.m_ptr;
        rhs.m_ptr = nullptr;
      }
    }
    ~Str() {
      if (m_ptr) {
        printf("dtor: freeing %p\n", m_ptr)
        free(m_ptr);
        m_ptr = nullptr;
      }
    }
};

void hd(Str&& str) {
  printf("str.m_ptr = %p\n", str.m_ptr);
}

int main() {
  Str a("hello world"); // duplicates 'hello world'.
  Str b(a); // creates another copy
  hd(std::move(b)); // transfers authority for b to function hd.
  //hd(b); // compile error
  printf("after hd, b.m_ptr = %p\n", b.m_ptr); // it's been moved.
}

En règle générale:

  • Le passage par valeur banal des objets,
  • Le passage par valeur si la destination a besoin d'une mutable copie,
  • Passer par const de référence pour les non-trivial objets, où le spectateur ne doit voir le contenu/l'état, mais n'a pas besoin d'être modifiables,
  • Se déplacer lorsque la destination a besoin d'une mutable copie temporaire du/de la valeur construite (par exemple, std::move(std::string("a") + std::string("b"))).
  • Déplacer lorsque vous avez besoin d'une localité de l'état de l'objet, mais souhaitez conserver l'existant/valeurs des données et la libération de l'actuel titulaire.

7voto

zdan Points 11822

N'oubliez pas que si vous ne transmettez pas une valeur r, le fait de passer par valeur produira une copie complète. En règle générale, le passage d'une valeur à l'autre peut donc nuire à la performance.

3voto

Matthew Lundberg Points 21747

Votre exemple est défectueux. C++11 ne vous donne pas un déplacement avec le code que vous avez, et une copie peut en être faite.

Cependant, vous pouvez obtenir un déménagement avec ceci:

int hd(vector<int>&& a) {
   return a[0];
}

// ...
std::vector<int> a = ...
return hd(a);

Ou de fonte de la valeur de référence rvalue:

std::vector<int> a = ...
int x = hd(std::move(a));

C'est en supposant que vous ne serez pas en utilisant la variable a dans votre fonction, sauf pour la détruire ou de lui affecter une nouvelle valeur.

Const références permettent temporaires silencieuse créé. Vous pouvez passer à quelque chose qui est approprié pour un constructeur implicite, et une temporaire sera créé. L'exemple classique est un tableau de char à être convertis en const std::string& mais avec std::vector, un std::initializer_list peuvent être converties.

Donc:

int hd(const std::vector<int>&); // Declaration of const reference function
int x = hd({1,2,3,4});

Et bien sûr, vous pouvez déplacer le temporaire ainsi:

int hd(std::vector<int>&&); // Declaration of rvalue reference function
int x = hd({1,2,3,4});

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