En règle générale, lorsque l'on ne souhaite pas fournir une fonction, il suffit simplement de ne pas la déclarer. Cette technique ne fonctionne ni pour le constructeur par copie ni pour l'opérateur d'affectation. Si on ne les a pas déclarés et qu'on tente de les utiliser, le compilateur les génèrera automatiquement. Voyez à ce sujet l'article concernant les constructeurs, destructeurs et opérateurs générées automatiquement par le compilateur.

Rendez privés le constructeur par copie et l'opérateur d'affectation

Pour désactiver le constructeur par copie et l'opérateur d'affectation, il suffit de les déclarer dans la partie privée de la classe et vous n'aurez même pas besoin de les définir. De ce fait, le compilateur ne les génèrera pas, et il sera impossible de les utiliser puisqu'ils sont privés. Ils pourront toutefois être appelés (par erreur) depuis la classe elle-même ou depuis une fonction amie, mais comme ils sont déclarés mais non définis, une erreur à l'édition des liens aura lieu. Cette technique est fiable, elle est utilisée dans de nombreuses bibliothèques du C++. Voici un petit exemple.

// Fichier UniqObject.h
class UniqObject
{
public:
  UniqObject() {}
  ~UniqObject() {}
 
private:
  // le nom n'est pas requis, mais on peut le laisser en fonction de ses habitudes.
  // les deux éléments suivants sont déclarés, mais pas définis.
  // le compilateur ne les redéclarera pas, et les utilisateurs ne peuvent les appeler 
  // puisqu'elles sont privées.
  UniqObject(const UniqObject& /*obj*/);
  UniqObject& operator=(const UniqObject& /*obj*/);
};
 
// Fichier main.cpp
#include <iostream>
#include "UniqObject.h"
 
int main(int argc, char ** argv)
{
  UniqObject x;
  //UniqObject y(x); // erreur
  UniqObject z;
  //z = x; // erreur
 
  return EXIT_SUCCESS;
}

Cette technique fonctionne très bien, elle possède cependant deux inconvénients. Le premier est qu'il faut à chaque fois déclarer le constructeur et l'opérateur d'affectation... Le second, de loin le plus embêtant, est que les erreurs ont lieu à l'édition des liens.

En ajoutant l'héritage

La technique suivante permet de générer les erreurs à la compilation. Il s'agit de créer une classe de base NonCopyable et de déclarer privés le constructeur par copie et l'opérateur d'affectation. On fait ensuite hériter de cette classe toutes celles auxquelles on souhaite interdire la copie. Voici un exemple.

// Fichier NonCopyable.h
 
class NonCopyable
{
protected:
  // le constructeur et le destructeur doivent être déclarés protégés.
  // le destructeur ne doit pas être virtuel.
  NonCopyable() {}
  ~NonCopyable() {}
 
private:
  // le constructeur par copie et l'opérateur d'affectation sont privés.
  NonCopyable(const NonCopyable& /*obj*/);
  NonCopyable& operator=(const NonCopyable& /*obj*/);
};
 
// Fichier UniqObject.h
#include "NonCopyable.h"
 
// l'héritage doit être privé.
class UniqObject : private NonCopyable
{
public:
  UniqObject() {}
  ~UniqObject() {}
 
  void test() const {
    UniqObject a;
    // erreur : même de l'intérieur de la classe. on ne peut pas faire de copie.
    //UniqObject b(a);
  }
};
 
// Fichier main.cpp
#include <iostream>
#include "UniqObject.h"
 
int main(int argc, char ** argv)
{
  UniqObject x;
  //UniqObject y(x); // erreur : on ne peut pas faire de copie.
  UniqObject z;
  //z = x; // erreur : on ne peut pas utiliser cet opérateur.
 
  return EXIT_SUCCESS;
}

Cette méthode est plus propre et plus claire. En effet, en voyant la définition de la classe, on sait tout de suite qu'elle n'est pas copiable puisqu'elle hérite de NonCopyable. Elle permet également d'écrire moins de code, même si ce gain est infime.

Utiliser boost::noncopyable

Le principe est le même avec boost::noncopyable. Et l'utilisation est la même. Voici un exemple.

// Fichier NonCopyable.h
#include <iostream>
#include <boost/noncopyable.hpp>
 
class UniqObject : private boost::noncopyable
{
public:
  UniqObject() {}
  ~UniqObject() {}
  void test() const {
    UniqObject a;
    // erreur : même de l'intérieur de la classe. on ne peut pas faire de copie.
    //UniqObject b(a);
  }
};
 
int main(int argc, char ** argv)
{
  UniqObject x;
  //UniqObject y(x); // erreur
  UniqObject z;
  //z = x; // erreur
 
  return EXIT_SUCCESS;
}

Conclusion

Il est parfois utile de pouvoir empêcher la copie d'objets. Utilisez de préférence les deux dernières techniques même si la premier fonctionne très bien. Le fait de rendre privé le constructeur par copie vous empêchera de retourner un objet depuis une fonction ou encore de passer un objet par copie. Vous devrez donc passer ces objets par référence (éventuellement constante), mais ce n'est pas déjà ce que vous faites ?

Vous pourrez approfondir le sujet avec la sélection des livres suivants : Pour mieux développer avec C++, Standards de programmation en C++ et Le langage C++.