In C++ möchte ich ein Objekt als Member einer Klasse wie folgt definieren:
Object myObject;
Dadurch wird jedoch versucht, seinen parameterlosen Konstruktor aufzurufen, der nicht vorhanden ist. Allerdings muss der Konstruktor aufgerufen werden, nachdem die enthaltende Klasse einige Initialisierungen durchgeführt hat. Etwas wie das.
class Program
{
public:
Object myObject; //Should not try to call the constructor or do any initializing
Program()
{
...
//Now call the constructor
myObject = Object(...);
}
}
Speichern Sie einen Zeiger auf eine Object
statt auf eine tatsächliche Object
.
somit:
class Program
{
public:
Object* myObject; // Will not try to call the constructor or do any initializing
Program()
{
//Do initialization
myObject = new Object(...); // Initialised now
}
}
Vergessen Sie nicht, delete
es im Destruktor zu machen. Modernes C++ hilft Ihnen dabei, indem Sie ein auto_ptr shared_ptr anstelle eines unformatierten Speicherzeigers.
Andere haben Lösungen veröffentlicht, die rohe Zeiger verwenden, aber ein intelligenter Zeiger wäre eine bessere Idee:
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
};
Dies vermeidet die Notwendigkeit, einen Destruktor hinzuzufügen, und verbietet implizit das Kopieren (es sei denn, Sie schreiben Ihren eigenen operator=
und MyClass(const MyClass &)
.
Wenn Sie eine separate Heap-Zuordnung vermeiden möchten, können Sie dies mit dem aligned_storage
von boost und der Platzierung new tun. Ungetestet:
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();
}
}
Wenn Object
kopierbar (oder verschiebbar) ist, können Sie boost::optional
verwenden:
class MyClass {
boost::optional<Object> obj;
public:
MyClass() {
// ...
obj = Object(...);
obj->foo();
}
};
Wenn Sie Zugriff auf Boost haben, gibt es ein praktisches Objekt mit dem Namen boost::optional<>
- dies vermeidet die Notwendigkeit einer dynamischen Zuweisung, z.
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;
};
Möglicherweise können Sie Ihren Code auch für die Verwendung der Konstruktor-Initialisierungsliste neu schreiben, wenn Sie die andere Initialisierung in Konstruktoren verschieben können:
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*/)
{
...
}
};
Sie müssen sich dessen bewusst sein, dass das Befolgen eines solchen Musters zu einem Pfad führt, auf dem Sie viel mit Konstruktoren arbeiten, was wiederum dazu führt, dass Sie Ausnahmebehandlung und -sicherheit (als kanonische Methode, um einen Fehler von einem Konstruktor zurückgeben zu können) benötigen ist eine Ausnahme zu werfen).
Sie können dazu einen Zeiger (oder einen intelligenten Zeiger) verwenden. Wenn Sie keinen intelligenten Zeiger verwenden, stellen Sie sicher, dass der Code frei ist, wenn das Objekt gelöscht wird. Wenn Sie einen intelligenten Zeiger verwenden, machen Sie sich keine Sorgen.
class Program
{
public:
Object * myObject;
Program():
myObject(new Object())
{
}
~Program()
{
delete myObject;
}
// WARNING: Create copy constructor and = operator to obey rule of three.
}
Sie können die Objektkonstruktion und -zerstörung durch diesen Trick vollständig steuern:
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;
};
};
Bewerben Sie sich auf Ihre Probe:
class Program
{
public:
DefferedObject<Object> myObject; //Should not try to call the constructor or do any initializing
Program()
{
...
//Now call the constructor
myObject.Construct(....);
}
}
Der große Vorteil dieser Lösung besteht darin, dass keine zusätzlichen Zuordnungen erforderlich sind und der Objektspeicher normal zugewiesen wird. Sie haben jedoch die Möglichkeit, den Konstruktor aufzurufen.