FAQ C++Consultez toutes les FAQ

Nombre d'auteurs : 35, nombre de questions : 368, dernière mise à jour : 17 novembre 2018  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.

Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.


SommaireLes classes en C++Sémantique de copie (7)
précédent sommaire suivant
 

Une classe copiable permet à ses instances d'être copiées à l'identique d'un contenant vers un autre. En C++, syntaxiquement cette propriété est vérifiée dès qu'une classe possède un constructeur par copie ou un opérateur d'affectation.

Code c++ : Sélectionner tout
1
2
copyable a; 
copyable b = a; // ou copyable b(a);
Sémantiquement, il faut néanmoins prendre garde à ce que la copie d'un objet n'entraîne pas des effets indésirables :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class copy_with_problems 
{ 
    public: 
    copy_with_problems() 
      : m_probleme(new Type()) 
    {} 
    copy_with_problems(copy_with_problems const & copie_) 
      : m_probleme(copie_.m_probleme) 
    {} 
  
    copy_with_problems& operator=(copy_with_problems const & rhs_) 
    { 
        delete m_probleme; 
        m_probleme = copie_.m_probleme; 
        return *this; 
    } 
  
    ~copy_with_problems() 
    { 
         delete m_probleme; 
    } 
  
private: 
    Type *m_probleme; 
}; 
  
  
int main() 
{ 
    copy_with_problems my_object; 
    { 
        copy_with_problems a_copy(my_object); 
    } // au sortir on détruit le pointeur de a_copy qui est le même que celui 
      // de my_object 
      // ici my_object.m_probleme pointe sur une zone invalide ! 
}

Mis à jour le 15 octobre 2009 3DArchi

Le compilateur génère des versions par défaut du constructeur par copie et de l'opérateur=.
Le constructeur par copie généré par le compilateur correspond à un appel du constructeur par copie des classes parentes et du constructeur par copie des membres.
Le comportement est le même pour l'opérateur = par défaut.
Notons toutefois que l'opérateur d'affectation ne peut être généré par le compilateur si la classe possède un membre constant ou un membre référence. Le constructeur par copie implicite est bien généré mais pas l'opérateur =.
De façon analogue, si la classe possède un membre non copiable alors ni le constructeur par copie ni l'opérateur d'affectation implicites ne peuvent être générés par le compilateur.
Peut-on autant dire que notre classe est copiable ? Non. Certes syntaxiquement un objet de cette classe peut être copié. Mais il faut encore vérifier si sémantiquement cela a une cohérence et ne risque pas de poser de problème. Une syntaxe correcte vous garantit que votre code compile, une sémantique bien définie assure une exécution maîtrisée.

Mis à jour le 15 octobre 2009 3DArchi

Non. Par exemple, une classe à sémantique d'entité ne doit pas être recopiée. De même certaines enveloppes RAII ( Resource Acquisition Is Initialization ) peuvent avoir une politique interdisant la copie (par exemple un ScopedMutex). Citons encore les singletons.

Mis à jour le 15 octobre 2009 3DArchi

Pour rendre une classe non copiable, il faut déclarer son constructeur par copie et son opérateur d'affectation en privé sans les définir :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
class non_copyable 
{ 
public: 
    non_copyable(){} 
    //... 
private: 
    non_copyable(non_copyable const &);// pas de définition ! 
    non_copyable& operator=(non_copyable const &);// pas de définition ! 
};
Les rendre private empêche de pouvoir les appeler à l'extérieur de la classe. Le compilateur génère une erreur si on tente de les appeler.
Ne pas les définir empêche de les appeler à l'intérieur de la classe. L'erreur apparaît alors à l'édition de lien.

Mis à jour le 15 octobre 2009 3DArchi

Si la classe dérivée ne redéfinit pas son constructeur par copie et/ou son opérateur d'affectation, la propriété non copiable est conservée :

Code c++ : Sélectionner tout
1
2
3
4
5
class derived : private non_copyable 
{ 
public: 
    derived(){} 
};
Cependant, la propriété peut se perdre si vous redéfinissez le constructeur par copie ou l'opérateur d'affectation sans faire appel à la méthode parente :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
class derived : private non_copyable 
{ 
public: 
    derived(){} 
    derived(derived const &){} 
    derived& operator=(derived const &) 
    { return *this; } 
}; 
  
    derived a; 
    derived b(a); // OK 
    derived c; 
    c = a;// OK
Le constructeur non_copyable appelé dans derived(derived const &) est ici le constructeur par défaut non_copyable().

Boost propose une classe boost::noncopyable avec un constructeur par copie et un opérateur d'affectation privés non définis :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
#include <boost/noncopyable.hpp> 
  
class my_class_non_copyable : private boost::noncopyable 
{ 
public: 
    my_class_non_copyable(){} 
};
L'héritage boost::noncopyable doit être privé. Une première raison est que boost::noncopyable ne possède pas de destructeur virtuel public ! Ensuite, l'utilisation polymorphe de boost::noncopyable n'a pas de sens. Enfin, boost::noncopyable ne proposant pas de membres utilitaires qui peuvent être appelés par les classes dérivées, un héritage protected ne se justifie pas.

Mis à jour le 15 octobre 2009 3DArchi

Nous avons donc deux solutions pour rendre une classe non copiable : déclarer son constructeur par copie et son opérateur d'affectation sans les définir ou hériter d'une classe non copiable (par exemple, boost::noncopyable). La seconde approche est préférable, en particulier car les compilateurs peuvent avoir des comportements différents sur des aspects particuliers. Par exemple, le SFINAE ( Substitution Failure Is Not An Error) dans le code suivant ne se comporte pas de façon identique sur visual et gcc :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream> 
  
template <typename T1, typename T2> 
struct CanConvert { 
    typedef char True; 
    class False { char dummy[2]; }; 
    static True canConvert( T2 ); 
    static False canConvert( ... ); 
    static T1 makeT1( ); 
    enum { r = sizeof(canConvert( makeT1( ) )) == sizeof(True) }; 
}; 
  
struct non_copyable { 
    non_copyable() {} 
private: 
    non_copyable(non_copyable const&); 
    non_copyable& operator=(non_copyable const&); 
}; 
  
struct S1 
{ 
    S1(int) {} 
private: 
    S1(S1 const& ); 
    S1& operator=(S1 const&); 
}; 
  
struct S2 
: private non_copyable 
{ 
    S2(int) {} 
private: 
}; 
  
int main (int argc, char **argv) 
{ 
    std::cout << CanConvert<char, S1>::r << std::endl; 
    std::cout << CanConvert<int*, S1>::r << std::endl; 
  
    std::cout << CanConvert<char, S2>::r << std::endl; 
    std::cout << CanConvert<int*, S2>::r << std::endl; 
  
}

Mis à jour le 15 octobre 2009 3DArchi Luc Hermitte

Le C++0x introduit un nouveau mécanisme facilitant l'écriture de classe non copiable : la suppression de fonction grâce à la syntaxe « =delete ».
Cette syntaxe permet de déclarer une fonction tout en supprimant sa définition. Elle peut s'appliquer aux fonctions membres spéciales (constructeurs, constructeur par copie, destructeur, opérateurs), ainsi qu'aux fonctions membres et aux fonctions libres - bref, à toutes les fonctions.
Pour rendre une classe non copiable, il faut supprimer son constructeur par copie et son opérateur d'affectation :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
class non_copyable 
{ 
public: 
    // force le compilateur à générer la version par défaut du constructeur. 
    non_copyable() = default; 
  
    // suppression du constructeur par copie et de l'opérateur d'affectation 
    non_copyable(const non_copyable &) = delete; 
    non_copyable & operator= (const non_copyable &) = delete; 
};
Cette approche possède quelques avantages par rapport à l'astuce utilisée en C++03 :
  • La syntaxe pour supprimer une fonction est claire et non-ambigüe.
  • L'appel à une fonction supprimée échoue à la compilation, jamais à l'édition des liens.
  • Le message d'erreur est plus explicite et pointe directement sur la fonction supprimée.

Notez également l'utilisation de la syntaxe « =defaut » pour récupérer un constructeur par défaut généré automatiquement par le compilateur.

Mis à jour le 15 octobre 2009 Arzar

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

 
Contacter le responsable de la rubrique C++

Partenaire : Hébergement Web