En C++, je veux définir un objet en tant que membre d'une classe comme ceci:
Object myObject;
Cependant, cela tentera 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 contenante ait initialisé. Quelque chose comme ça.
class Program
{
public:
Object myObject; //Should not try to call the constructor or do any initializing
Program()
{
...
//Now call the constructor
myObject = Object(...);
}
}
Stocker un pointeur sur une Object
plutôt qu'une réelle 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 y aide, en ce sens que vous pouvez utiliser un auto_ptr shared_ptr plutôt qu'un pointeur de mémoire brute.
D'autres ont publié des solutions en 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'ajouter un destructeur et interdit implicitement la copie (sauf si vous écrivez vos propres operator=
et MyClass(const MyClass &)
.
Si vous souhaitez éviter une allocation distincte de segment de mémoire, vous pouvez le faire avec aligned_storage
de boost et le placement new. 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 boost::optional
:
class MyClass {
boost::optional<Object> obj;
public:
MyClass() {
// ...
obj = Object(...);
obj->foo();
}
};
Si vous avez accès à Boost, un objet très pratique, appelé boost::optional<>
, est fourni. Cela évite le recours à une 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;
};
Vous pourrez peut-être également réécrire votre code pour utiliser la liste d'initialisation du constructeur, si vous pouvez déplacer l'autre initialisation en 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 savoir que suivre un tel modèle vous mènera à un chemin dans lequel vous effectuerez beaucoup de travail dans les constructeurs, ce qui vous obligera à saisir la gestion des exceptions et la sécurité (en tant que moyen canonique de renvoyer une erreur d'un constructeur est de jeter une exception).
Vous pouvez utiliser un pointeur (ou un pointeur intelligent) pour le faire. Si vous n'utilisez pas de pointeur intelligent, assurez-vous que votre code libère de la mémoire lorsque l'objet est supprimé. Si vous utilisez un pointeur intelligent, ne vous inquiétez pas.
class Program
{
public:
Object * myObject;
Program():
myObject(new Object())
{
}
~Program()
{
delete myObject;
}
// WARNING: Create copy constructor and = operator to obey rule of three.
}
Vous pouvez totalement contrôler la construction et la destruction de l'objet par cette astuce:
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 gros avantage de cette solution est qu’elle ne nécessite aucune allocation supplémentaire ni mémoire d’objet allouée normalement, mais vous avez le contrôle lorsque vous appelez le constructeur.