web-dev-qa-db-fra.com

Quels sont les avantages de boost :: noncopyable

Pour empêcher la copie d'une classe, vous pouvez très facilement déclarer un constructeur de copie privée/des opérateurs d'affectation. Mais vous pouvez également hériter boost::noncopyable.

Quels sont les avantages/inconvénients de l'utilisation de boost dans ce cas?

63
tenfour

Résumant ce que les autres ont dit:

Les avantages de boost::noncopyable sur les méthodes de copie privée:

  1. Il est plus explicite et descriptif dans l'intention. L'utilisation de fonctions de copie privée est un idiome qui prend plus de temps à repérer que noncopyable.
  2. C'est moins de code/moins de frappe/moins d'encombrement/moins de marge d'erreur (le plus simple serait de fournir accidentellement une implémentation).
  3. Il intègre la signification directement dans les métadonnées du type, semblable à un attribut C #. Vous pouvez désormais écrire une fonction qui n'accepte que les objets non copiables.
  4. Il détecte potentiellement des erreurs plus tôt dans le processus de génération. L'erreur sera présentée au moment de la compilation plutôt qu'au moment du lien, dans le cas où la classe elle-même ou les amis de la classe font la copie erronée.
  5. (presque la même chose que # 4) Empêche la classe elle-même ou les amis de la classe d'appeler les méthodes de copie privée.

Avantages des méthodes de copie privée sur boost::noncopyable:

  1. Pas de dépendance au boost
41
tenfour

Je ne vois aucun avantage documentaire:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
};

contre:

struct A
{
     A(const A&) = delete;
     A& operator=(const A&) = delete;
};

Lorsque vous ajoutez des types de déplacement uniquement, je considère même la documentation comme trompeuse. Les deux exemples suivants ne sont pas copiables, bien qu'ils soient mobiles:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
    A(A&&) = default;
    A& operator=(A&&) = default;
};

contre:

struct A
{
    A(A&&) = default;
    A& operator=(A&&) = default;
};

En cas d'héritage multiple, il peut même y avoir une pénalité d'espace:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
};

struct B
    : public A
{
    B();
    B(const B&);
    B& operator=(const B&);
};

struct C
    : public A
{
};

struct D
    : public B,
      public C,
      private boost::noncopyable
{
};

#include <iostream>

int main()
{
    std::cout << sizeof(D) << '\n';
}

Pour moi, cela imprime:

3

Mais ceci, que je crois avoir une documentation supérieure:

struct A
{
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};

struct B
    : public A
{
    B();
    B(const B&);
    B& operator=(const B&);
};

struct C
    : public A
{
    C(const C&) = delete;
    C& operator=(const C&) = delete;
};

struct D
    : public B,
      public C
{
    D(const D&) = delete;
    D& operator=(const D&) = delete;
};

#include <iostream>

int main()
{
    std::cout << sizeof(D) << '\n';
}

Les sorties:

2

Je trouve qu'il est beaucoup plus facile de déclarer mes opérations de copie que de déterminer si je dérive de boost::non_copyable plusieurs fois et si cela va me coûter cher. Surtout si je ne suis pas l'auteur de la hiérarchie d'héritage complète.

46
Howard Hinnant

Cela rend l'intention explicite et claire, sinon il faut voir la définition de la classe et rechercher la déclaration liée à la copie-sémantique, puis rechercher le spécificateur d'accès dans lequel il se trouve - déclaré, afin de déterminer si la classe est non copiable ou non. Autre moyen de le découvrir en écrivant du code qui nécessite la copie sémantique activée et de voir l'erreur de compilation.

42
Nawaz
  1. L'intention de boost :: noncopyable est plus claire.
  2. Boost :: noncopyable empêche les méthodes de classes d'utiliser accidentellement le constructeur de copie privée.
  3. Moins de code avec boost :: noncopyable.
16
thiton

Je ne comprends pas pourquoi personne d'autre ne semble le mentionner, mais:

Avec noncopyable, vous écrivez le nom de votre classe une seule fois.

Sans, multiplication par cinq: un A pour la "classe A", deux pour désactiver l'affectation et deux pour désactiver le constructeur de copie.

16
ansgri

Citant la documentation:

"La manière traditionnelle de traiter ces problèmes consiste à déclarer un constructeur de copie privée et une affectation de copie, puis à expliquer pourquoi cela est fait. Mais dériver de non copiable est plus simple et plus clair, et ne nécessite pas documentation supplémentaire. "

http://www.boost.org/libs/utility/utility.htm#Class_noncopyable

9
Viktor Sehr

Un avantage concret (au-delà d'exprimer un peu plus clairement votre intention) est que l'erreur sera détectée plus tôt, à l'étape de compilation et non à l'étape de liaison, si une fonction membre ou ami essaie de copier un objet. Le constructeur/affectation de la classe de base n'est accessible nulle part, ce qui donne une erreur de compilation.

Cela vous empêche également de définir accidentellement les fonctions (par exemple, en tapant {} au lieu de ;), une petite erreur qui pourrait bien passer inaperçue, mais qui permettrait alors aux membres et amis de faire des copies invalides de l'objet.

8
Mike Seymour

Un petit inconvénient (spécifique à GCC) est que, si vous compilez votre programme avec g++ -Weffc++ et vous avez des classes contenant des pointeurs, par exemple.

class C : boost::noncopyable
{
public:
  C() : p(nullptr) {}

private:
  int *p;
};

GCC ne comprend pas ce qui se passe:

avertissement: 'classe C' a des membres de données de pointeur [-Weffc ++]
avertissement: mais ne remplace pas "C (const S &)" [-Weffc ++]
avertissement: ou 'operator = (const C &)' [-Weffc ++]

Bien qu'il ne se plaindra pas de:

#define DISALLOW_COPY_AND_ASSIGN(Class) \
  Class(const Class &) = delete;     \
  Class &operator=(const Class &) = delete

class C
{
public:
  C() : p(nullptr) {}
  DISALLOW_COPY_AND_ASSIGN(C);

private:
  int *p;
};

PS Je sais que -Weffc ++ de GCC a plusieurs problèmes. Le code qui vérifie les "problèmes" est assez simpliste, de toute façon ... parfois cela aide.

3
manlio

L'avantage est que vous n'avez pas à écrire vous-même un constructeur de copie privée et un opérateur de copie privée et cela exprime clairement votre intention sans écrire de documentation supplémentaire.

3
Nikko

Je préfère utiliser boost :: noncopyable que supprimer ou privatiser manuellement le constructeur de copie et l'opérateur d'affectation.

Cependant, je n'utilise presque jamais la méthode non plus, car:

Si je crée un objet non copiable, il doit y avoir une raison pour laquelle il n'est pas copiable. Cette raison, 99% du temps, est parce que j'ai des membres qui ne peuvent pas être copiés de manière significative. Il y a de fortes chances que ces membres soient également mieux adaptés en tant que détails d'implémentation privés. Je fais donc la plupart de ces classes comme ceci:

struct Whatever {
  Whatever();
  ~Whatever();
  private:
  struct Detail;
  std::unique_ptr<Detail> detail;
};

Alors maintenant, j'ai une structure d'implémentation privée, et depuis que j'ai utilisé std :: unique_ptr, ma classe de niveau supérieur n'est pas copiable gratuitement. Les erreurs de lien qui en découlent sont compréhensibles car elles expliquent comment vous ne pouvez pas copier un std :: unique_ptr. Pour moi, ce sont tous les avantages de boost :: noncopyable et une implémentation privée intégrée.

L'avantage de ce modèle est plus tard, si je décide que je voulais vraiment rendre mes objets de cette classe copiables, je peux simplement ajouter et implémenter un constructeur de copie et/ou un opérateur d'affectation sans changer la hiérarchie des classes.

2
wjl

L'inconvénient, selon Scott Meyers, le nom est "non natrual", si vous avez besoin d'en trouver un inconvénient.

1
H Xu