3

I have an instance of a class object that I want to reset to its original state. and it has a well tested constructor. Normally I would do this with code like

myObject = MyObject{};

but the temporary object is too large to fit on the stack.

My options are

1) Create a default temporary object on the heap

auto* tempObject = make_unique<MyObject>();
myObject = *tempObject;

2) Destroy and reconstruct in place with placement new

myObject.~MyObject();
new( &myObject) MyObject();

3) Refactor constructor code into a separate reset function that is called from the constructor and from anywhere else.

MyObject::MyObject()
{
   reset();
}
//and
myObject.reset();

I don't like any of the options particularly: 1) allocates on the heap in a project that tries to avoid unnecessary allocations 2) is fragile and smelly Can I use placement new(this) in operator=? 3) I'm lazy and want to avoid refactoring if I can.

I guess what I'm really asking is are there other ways I've missed? Ideally Is there some sort of optimisation that could apply that means the compiler can elide creating the temporary item on the stack and allow me to use my original code?

David Woo
  • 749
  • 4
  • 13
  • Does `MyObject` have any const or ref members? If not the second option seems reasonable. – Rakete1111 Jun 14 '18 at 10:16
  • 5
    http://www.gotw.ca/gotw/023.htm – Maxim Egorushkin Jun 14 '18 at 10:21
  • Don't think (2) is smelly and fragile in this case. It would be if you called it on `this` in an assignment operator, but here it seems fine to me. – Baldrick Jun 14 '18 at 10:38
  • 1
    What's the constraint on allocations on the heap in your project? If you can control when and how much, would it still be a problem? You could allocate once for that instance and use placement new for each subsequent reset. Or am I missing something? – JBL Jun 14 '18 at 10:39
  • 2
    @Baldrick The problem with (2) is that in case of throwing constructor you may end up with undefined object state. – user7860670 Jun 14 '18 at 10:41
  • 4
    I think that (1) and (2) will involve writing some sort of `reset` function as well for the sake of encapsulation. Dedicated `reset` function has a clear purpose and is likely to be non-throwing (unlike initial constructor calls). So (3) seems like an obvious choice. – user7860670 Jun 14 '18 at 10:44
  • 1
    @VTT I agree. Sometimes you've just got to do the hard yards. The only other viable option, for me, would be to assess accurately the cost of option 1 and decide if that is more palateable, – Paul Sanders Jun 14 '18 at 10:52
  • 1
    @Rakete1111 It doesn't have any const or ref members but does inherit from a base class. Currently the base class has no members and so 2) is currently safe but the base class could change and break this code which is why I called it fragile. – David Woo Jun 14 '18 at 10:56
  • Turn your object into a smart pointer, then simply create a new instance using `new`, assign it to the pointer, and let the smart pointer take care of destroying the old object. – Sam Varshavchik Jun 14 '18 at 11:01
  • @SamVarshavchik, if you don't care about performance at all. – Smit Ycyken Jun 14 '18 at 11:42
  • @VTT You are completely right and in fact the code I want to fix is already a reset function calling a constructor so its really just a case of rewriting the constructor in terms of the reset rather the reset in terms of the constructor. – David Woo Jun 14 '18 at 12:41
  • What about digging into the compiler options to increase the stack size? Even MSVC has options for it... And of course carefully document it. – Serge Ballesta Jun 14 '18 at 12:46
  • @JBL You're right, allocating once is a viable option and better than allocating everytime a reset is needed. Another option I hadn't considered earlier is to have a static const instance of the class that I can reset from. This would be expensive if I had many classes like this but currently its only the stackspace that is limited. – David Woo Jun 14 '18 at 12:47

2 Answers2

1

The best - is always the opinion based solution.

In the common practice, if you use OOP and want to reset the object, write the Reset function, which would do exactly what you want in the exact manner you want it to be done.

You have base classes, so you can provide the same pattern without breaking encapsulation or making fragile, misleading solutions.

p.s. RAII vs zombie

Smit Ycyken
  • 1,189
  • 1
  • 11
  • 25
  • one thing to consider is, why you want to do the reset. If this is a hot object and you want to save the allocation cost you don't necessarily need to write a reset function. you can also write an initialize, the advantage of this approach is that you don't have invalid objects. – Alexander Oh Jun 14 '18 at 11:54
  • @Alex, lazy inits leads to zombie objects=) – Smit Ycyken Jun 14 '18 at 11:58
  • it depends how you do it right? if you ask for everything in the constructor AND in the init, you can reuse an object and you don't get to call "reset", which makes the zombie object. – Alexander Oh Jun 14 '18 at 12:15
1

A standard container could be a workaround:

std::vector<MyObject> v(1);    // require a capacity of 1

v.emplace_back();              // ok v[0] is now a ref to a heap constructed object

v.pop_back();                  // destroys the object
v.emplace_back();              // and rebuilds it normally without any (de-)allocation

But it is still a hack, when my opinion is that the correct solution is to increase the stack size. All decent compilers have options for it.

Anyway, a reset method can also be a nice and clean solution, provided it makes sense on a functional point of view (the object is designed to be resetable). If not, it is again a hack.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252