4

There's a point in my program where the state of a certain object needs to be reset "to factory defaults". The task boils down to doing everything that is written in the destructor and constructor. I could delete and recreate the object - but can I instead just call the destructor and the constructor as normal objects? (in particular, I don't want to redistribute the updated pointer to the new instance as it lingers in copies in other places of the program).

  MyClass {
  public:
        MyClass();
        ~MyClass();

  ...      
  }


  void reinit(MyClass* instance)
  {
        instance->~MyClass();
        instance->MyClass();
  }

Can I do this? If so, are there any risks, caveats, things I need to remember?

SF.
  • 13,549
  • 14
  • 71
  • 107
  • 2
    Constructors are special unnamed member functions and cannot be invoked directly like that. – Captain Obvlious Sep 15 '15 at 15:40
  • 1
    If you want to reset to factory defaults then why not use `foo = class_name();`? – NathanOliver Sep 15 '15 at 15:45
  • @NathanOliver: I'd need to redistribute updated 'foo' wherever it's needed. It's not a superglobal and the one instance is used in many places (access to a single, shared resource, a bit like a singleton). Doable but cumbersome. – SF. Sep 15 '15 at 15:50
  • this is very questionable idea. Actually calling constructor doesn't guarantee return to initial preconstructed state. You really should just create new objects where you need it, it is safer. – Andrey Sep 15 '15 at 15:52
  • @Andrey: I really don't need any deep memory magic - just reset several variables and re-establish one socket connection. This *must* fail gracefully though as the connection is not guaranteed and it should retry until the connection becomes available. (not within the constructor of course; just the reset performed periodically until successful.) – SF. Sep 15 '15 at 16:05
  • 1
    @SF. this is error prone approach, why introduce complex mutability instead of creating new instance? It will guarantee you correct state and you don't need any extra tricks like how to call constructor. – Andrey Sep 15 '15 at 16:44

6 Answers6

3

If your assignment operator and constructor are written correctly, you should be able to implement this as:

void reinit(MyClass* instance)
{
    *instance = MyClass();
}

If your assignment operator and constructor are not written correctly, fix them.

The caveat of implementing the re-initialisation as destruction followed by construction is that if the constructor fails and throws an exception, the object will be destructed twice without being constructed again between the first and second destruction (once by your manual destruction, and once by the automatic destruction that occurs when its owner goes out of scope). This has undefined behaviour.

Mankarse
  • 39,818
  • 11
  • 97
  • 141
2

You could use placement-new:

void reinit(MyClass* instance)
{
    instance->~MyClass();
    new(instance) MyClass();
}

All pointers remain valid.

Or as a member function:

void MyClass::reinit()
{
    ~MyClass();
    new(this) MyClass();
}

This should be used carefully, see http://www.gotw.ca/gotw/023.htm, which is about implementing an assignement operator with this trick, but some points apply here too:

  • The constructor should not throw
  • MyClass should not be used as a base class
  • It interferes with RAII, (but this could be wanted)

Credit to Fred Larson.

Community
  • 1
  • 1
alain
  • 11,939
  • 2
  • 31
  • 51
  • I like this, especially the fragment when you're performing code inside a method of an instance that doesn't exist anymore and re-create it a line later :) – SF. Sep 15 '15 at 16:07
  • Yes, that's ok as long as no members of `this` are acessed. – alain Sep 15 '15 at 16:09
  • 3
    I wouldn't recommend this. See http://www.gotw.ca/gotw/023.htm Not all of the problems there apply to this scenario, but some do, like problems 1, 2, 4, and 5. – Fred Larson Sep 15 '15 at 16:21
  • Unfortunately, my constructor definitely can throw. – SF. Sep 16 '15 at 19:26
  • You could theoretically still catch it in the constructor and somehow mark the object as 'invalid'. But for production code the accepted answer is surely the best way. – alain Sep 16 '15 at 19:47
1

Can I do this? If so, are there any risks, caveats, things I need to remember?

No you can't do this. Besides it's technically possible for the destructor call, it will be just undefined behavior.


Supposed you have implemented the assignment operator of your class correctly, you could just write:

void reinit(MyClass* instance) {
    *instance = MyClass();
}
Community
  • 1
  • 1
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
1

You should use a smart pointer and rely on move semantics to get the behavior you want.

auto classObj = std::make_unique<MyClass>();

This creates a wrapped pointer that handles the dynamic memory for you. Suppose you are ready to reset classObj to the factory defaults, all you need is:

classObj = std::make_unique<MyClass>();

This "move-assignment" operation will call the destructor of MyClass, and then reassign the classObj smart pointer to point to a newly constructed instance of MyClass. Lather, rinse, repeat as necessary. In other words, you don't need a reinit function. Then when classObj is destroyed, its memory is cleaned up.

KyleKnoepfel
  • 1,426
  • 8
  • 24
1

instance->MyClass(); is illegal, you must get a compilation error.

instance->~MyClass(); is possible. This does one of two things:

  • Nothing, if MyClass has a trivial destructor
  • Runs the code in the destructor and ends the lifetime of the object, otherwise.

If you use an object after its lifetime is ended, you cause undefined behaviour.

It is rare to write instance->~MyClass(); unless you either created the object with placement new in the first place, or you are about to re-create the object with placement new.

In case you are unaware, placement new creates an object when you already have got storage allocated. For example this is legal:

{
    std::string s("hello");
    s.~basic_string();
    new(&s) std::string("goodbye");

    std::cout << s << '\n';
}
M.M
  • 138,810
  • 21
  • 208
  • 365
0

You can try using placement new expression

new (&instance) MyClass()
Andrey
  • 59,039
  • 12
  • 119
  • 163