3

Suppose I have an object with a member that is expensive to construct, and the need for a reset() function that resets it to its initial state:

struct Example
{
    // option 1: efficient, but need to duplicate initialization logic of reset() 
    Example(int param) : expensive_member_(param), param_(param)
    {
    }
    /* option 2: less code, but initializes expensive_member_ twice
    Example(int param) : param_(param)
    { 
        reset();
    }*/

    void reset()
    {
         expensive_member_ = ClassWithExpensiveConstructor(param_);
    }

    ClassWithExpensiveConstructor expensive_member_;
    int param_;
}

Is there a better way of/pattern for efficiently resetting the object to its initial state without duplicating the initialization logic in the initializer list and the reset function?

edit: If there is no generic way of achieving what I want, that is also an acceptable outcome of this question!

anderas
  • 5,744
  • 30
  • 49
  • 3
    You could give `ClassWithExpensiveConstructor` a reset method of its own that does the most efficient reset possible. – Jonathan Potter Jul 27 '15 at 09:11
  • why not make expensive_member_ a pointer? – Hcorg Jul 27 '15 at 09:12
  • @JonathanPotter wouldn't that just move the problem somewhere else? I.e. I would have to worry about the duplicate code in `ClassWithExpensiveConstructor` instead of `Example`? – anderas Jul 27 '15 at 09:14
  • Really depends on your case. Maybe have a pool of ClassWithExpensiveConstructor objects and reuse them, assuming it's possible? I don't think there is a good generic solution. – Andre Jul 27 '15 at 09:26
  • 1
    To extend on Jonathan's comment: Resetting a class might be cheaper, than newly creating one. E.g. some containers could reuse the existing capacity. – MikeMB Jul 27 '15 at 11:00
  • Even then, however, the pointer solution gives you a cheap move constructor and assignment operator if you need them, so it might be advantageous to combine both approaches. – MikeMB Jul 27 '15 at 11:09

3 Answers3

1

If ClassWithExpensiveConstructor is the one class whose construction/reset is expensive, it should be it which optimizes the operation.

Another option would be to keep a copy of the initial value in a const member, assuming that copy construction/assignment is not expensive. That would use more memory, but improve performance if you happen to call Example::reset() a lot.

struct Example
{
    Example(int param)
    : expensive_member_backup_(param)
    , expensive_member_(expensive_mamber_backup)
    , param_(param)
    {
    }

    void reset()
    {
         expensive_member_ = expensive_member_backup_;
    }

    const ClassWithExpensiveConstructor expensive_member_backup_;
    ClassWithExpensiveConstructor expensive_member_;
    int param_;
}
Antonio Pérez
  • 6,702
  • 4
  • 36
  • 61
1

You can make your ExpensiveMember a pointer, in such case your option 2 will not call ExpensiveMember constructor in Example constructor unless you explicitly call it:

struct Example
{
    Example(int param) : expensive_member_(), param_(param)
    { 
         reset();
    }

    ~Example() {
         delete expensive_member_;   // better use unique_ptr etc
    }

    // Also a copy constructor and assignment operator 
    // are needed to avoid memory problems such as double-delete.
    // Also, better use unique_ptr etc.
    // This answer does not use unique_ptr just to make the main idea more clear.

    void reset()
    {
         delete expensive_member_;   // better use unique_ptr etc
         expensive_member_ = new ClassWithExpensiveConstructor(param_);
    }

    ClassWithExpensiveConstructor* expensive_member_;  // better use unique_ptr etc
    int param_;
}
Petr
  • 9,812
  • 1
  • 28
  • 52
  • @anderas, fixed. Did not want to use `unique_ptr` in answer so that the main idea (the pointer does not require initialization) is not hidden. – Petr Jul 27 '15 at 10:21
  • 2
    Better :-) Though, it's not necessary to check for `nullptr` befor deleting the pointer. Would you mind if I accepted @kiviak's solution? Your posts have the same basic idea, but he was quicker so I think that would be fair. – anderas Jul 27 '15 at 10:24
  • @anderas, you may accept what you like more, but actually my answer is earlier... :) – Petr Jul 27 '15 at 10:48
  • oops, sorry, my bad! Hard to decide which answer is better. Both look equally good to me... – anderas Jul 27 '15 at 10:53
  • @anderas: having accepted this answer shows me I misundertood your question, since I assumed that `expensive_member_` needed to be initialized in the constructor. In this case, I find it a design flaw that you may have an object with an uninitialized member: it ignores RAII. If you prefer not to construct the `expensive_member_` at `Example`'s construction time, then I would implement a lazy initialization mechanism within `Example` instead of forcing the client code to call `reset()`. – Antonio Pérez Jul 27 '15 at 13:33
  • 1
    @AntonioPérez, note that in this answer, `reset()` is called by the constructor. – Petr Jul 27 '15 at 13:35
  • @Petr: then what is the benefit over the OP's code? – Antonio Pérez Jul 27 '15 at 13:37
  • 1
    @AntonioPérez, over version 1 --- no code duplication (in constructor list and in `reset()`); over version 2 --- no implicit call to expensive constructor in `Example` constructor. – Petr Jul 27 '15 at 13:38
  • @AntonioPérez: right, the last comment of Petr is spot-on. – anderas Jul 27 '15 at 13:40
  • Ok for version 1, if code looks like that in kiviak's answer. But for option 2, both kiviak's and your answer implicitly call expensive constructor in `Example`'s. Besides, you are using dynamic memory allocation when not strictly needed. – Antonio Pérez Jul 27 '15 at 13:47
  • @AntonioPérez, I don't quite get it --- my answer has one version only. – Petr Jul 27 '15 at 13:49
  • @Petr: I meant that I don't get your claimed improvement over option 2 in your previous comment. – Antonio Pérez Jul 27 '15 at 13:58
  • @AntonioPérez, in OP's option 2, the `expensive_member_` will first be initialized with a default constructor (because it is not mentioned in member initializer list), and then overwritten in a call to `reset()`. In my solution, the default constructor is not called. – Petr Jul 27 '15 at 14:01
1

A simple solution would be to use a (smart or regular) pointer, so that the cost of initializing the member (i.e. the pointer) becomes smaller, and the actual object is only initialized in the call to reset():

 struct Example
 {
    Example(int param) : param_(param)
    { 
        reset();
    }

    void reset()
    {
         p.reset(new ClassWithExpensiveConstructor(param_));
    }

    unique_ptr<ClassWithExpensiveConstructor> p;
    int param_;
}
anderas
  • 5,744
  • 30
  • 49
kiviak
  • 1,083
  • 9
  • 10
  • This does seem like the best idea, though a small explanation would be nice, and `unique_ptr` seems a bit better suited for this job (less overhead and the shared ownership isn't needed here). Would you mind if I (or you) edited the answer accordingly? – anderas Jul 27 '15 at 10:20
  • @anderas,i dont mind.i am not good at smart pointer,but i think you can use it as a proxy pattern – kiviak Jul 27 '15 at 10:25