54 votes

Définir un objet sans appeler son constructeur en C++.

En C++, je veux définir un objet comme membre d'une classe comme ceci :

Object myObject;

Cependant, en faisant cela, on essaie d'appeler son constructeur sans paramètre, qui n'existe pas. Cependant, j'ai besoin que le constructeur soit appelé après que la classe qui le contient ait fait quelques initialisations. Quelque chose comme ceci.

class Program
{
public:
   Object myObject; //Should not try to call the constructor or do any initializing
   Program()
   {
      ...

      //Now call the constructor
      myObject = Object(...);
   }

}

33voto

Julian Points 1441

Stocker un pointeur vers un Object plutôt qu'un véritable Object

ainsi :

class Program
{
public:
   Object* myObject; // Will not try to call the constructor or do any initializing
   Program()
   {
      //Do initialization
      myObject = new Object(...);  // Initialised now
   }

}

N'oubliez pas de delete dans le destructeur. Le C++ moderne vous aide dans ce domaine, dans la mesure où vous pouvez utiliser une balise auto_ptr shared_ptr plutôt qu'un pointeur mémoire brut.

18voto

bdonlan Points 90068

D'autres ont publié des solutions utilisant des pointeurs bruts, mais un pointeur intelligent serait une meilleure idée :

class MyClass {
  std::unique_ptr<Object> pObj;
  // use boost::scoped_ptr for older compilers; std::unique_ptr is a C++0x feature
public:
  MyClass() {
    // ...
    pObj.reset(new Object(...));
    pObj->foo();
  }
  // Don't need a destructor
};

Cela évite d'avoir à ajouter un destructeur, et interdit implicitement la copie (à moins que vous n'écriviez votre propre fichier de type operator= y MyClass(const MyClass &) .

Si vous voulez éviter une allocation séparée du tas, vous pouvez utiliser la fonction de boost aligned_storage et un nouveau placement. Non testé :

template<typename T>
class DelayedAlloc : boost::noncopyable {
  boost::aligned_storage<sizeof(T)> storage;
  bool valid;
public:
  T &get() { assert(valid); return *(T *)storage.address(); }
  const T &get() const { assert(valid); return *(const T *)storage.address(); }

  DelayedAlloc() { valid = false; }

  // Note: Variadic templates require C++0x support
  template<typename Args...>
  void construct(Args&&... args)
  {
    assert(!valid);
    new(storage.address()) T(std::forward<Args>(args)...);
    valid = true;
  }

  void destruct() {
    assert(valid);
    valid = false;
    get().~T();
  }

  ~DelayedAlloc() { if (valid) destruct(); }
};

class MyClass {
  DelayedAlloc<Object> obj;
public:
  MyClass() {
    // ...
    obj.construct(...);
    obj.get().foo();
  }
}

Ou, si Object est copiable (ou déplaçable), vous pouvez utiliser la fonction boost::optional :

class MyClass {
  boost::optional<Object> obj;
public:
  MyClass() {
    // ...
    obj = Object(...);
    obj->foo();
  }
};

7voto

Evgeny Mamontov Points 191

Cette astuce vous permet de contrôler entièrement la construction et la destruction des objets :

template<typename T>
struct DefferedObject
{
    DefferedObject(){}
    ~DefferedObject(){ value.~T(); }
    template<typename...TArgs>
    void Construct(TArgs&&...args)
    {
        new (&value) T(std::forward<TArgs>(args)...);
    }
public:
    union
    {
        T value;
    };
};

Appliquez sur votre échantillon :

class Program
{
public:
   DefferedObject<Object> myObject; //Should not try to call the constructor or do any initializing
   Program()
   {
      ...

      //Now call the constructor
      myObject.Construct(....);
   }

}

Le grand avantage de cette solution est qu'elle ne nécessite pas d'allocations supplémentaires, et que la mémoire de l'objet est allouée normalement, mais vous avez le contrôle quand vous appelez le constructeur.

Un autre exemple de lien

5voto

Nim Points 22570

Si vous avez accès à boost, il y a un objet pratique qui est fourni appelé boost::optional<> - Cela évite de devoir recourir à l'allocation dynamique, par exemple.

class foo
{
  foo()  // default std::string ctor is not called..
  {
    bar = boost::in_place<std::string>("foo"); // using in place construction (avoid temporary)
  }
private:
  boost::optional<std::string> bar;
};

5voto

Joris Timmermans Points 8075

Vous pouvez également réécrire votre code pour utiliser la liste d'initialisation des constructeurs, si vous pouvez déplacer les autres initialisations dans les constructeurs :

class MyClass
  {
    MyObject myObject; // MyObject doesn't have a default constructor
  public:
    MyClass()
      : /* Make sure that any other initialization needed goes before myObject in other initializers*/
      , myObject(/*non-default parameters go here*/)
      {
      ...
      }
  };

Vous devez être conscient que suivre un tel modèle vous mènera vers un chemin où vous ferez beaucoup de travail dans les constructeurs, ce qui vous obligera à comprendre la gestion et la sécurité des exceptions (car la façon canonique de renvoyer une erreur depuis un constructeur est de lancer une exception).

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