1

Say I have a class like so:

class Foo{
    Foo* otherFooObj;
    Foo* otherFooObj2;
.....
};

Would it better to:

A.

  1. Acquire the correct values for member variables (addresses for otherFooObj and otherFooObj2, etc.) using functions
  2. Instantiate (construct) a Foo object with those correct values.
  3. Push the object to a vector.

OR

B.

  1. Instantiate a Foo object with dummy values (dummy addresses for otherFooObj and otherFooObj2)
  2. Push the object to a vector.
  3. Use functions to set the Foo object's member variables to correct values once it sits inside the vector.

OR

C.

  1. Instantiate a Foo object with dummy values
  2. Use functions to acquire correct values and set Foo's member variables to those values
  3. Push the object to a vector.

Right now, I'm thinking there isn't much difference between any of these design patterns, and that it doesn't matter which flow I follow. But I would like to get more opinions on this, and want to know if there are any other factors I'm overlooking and I should consider (which I feel like there are, maybe with pointers and such).

LazerSharks
  • 3,089
  • 4
  • 42
  • 67

2 Answers2

2

The question is a bit too generic to be answered appropriately. A good design goal is to only have objects in a valid state, by some definition of valid in your design. The alternative is not to be able to trust that an object is in a valid state before hand, and that will complicate your live and lead you to situations in which by mistake a half-baked object lives around.

From that point of view, I would create the values for the members and call a constructor that moves the object from a non-existent to that valid state.

In your case, it seems that you are building a self-referential data structure: a graph, a tree or something alike. If that is the case, it might be that a Foo with the two members set to nullptr make sense and you could consider the alternatives, but I would still provide a constructor that enables you to set the object to a full state:

class Foo {
   Foo *a, *b;
public:
   Foo() : a(), b() {} // initialize both to nullptr, assuming this is a *valid* state
   Foo(Foo *a, Foo *b) : a(a), b(b) {}
...
};

Coming back to the original question, I would opt to create the values for the members and emplace into the vector:

Foo *a = f();
Foo *b = f();
vector.emplace_back(a, b);
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Yep, I am building a self-referential data structure. Thank you for this helpful answer, introducing the ideas of valid state, half-baked objects and pointing out emplace_back. Researching emplace, I landed on this helpful article: http://stackoverflow.com/questions/4303513/push-back-vs-emplace-back Which mentions the ability of emplace to avoid half-baked and invalid states. Makes a lot of sense. – LazerSharks Nov 28 '14 at 21:25
  • 1
    @Gnuey: you don't really *need* `emplace_back` for that, you could be creating the object and then pushing it: `Foo value(a,b); vector.push_back(value);` (although it would not be as efficient) – David Rodríguez - dribeas Nov 28 '14 at 21:31
  • Ah, so emplace has some efficiency bonus' too. That's great because I'm definitely going to need any extra performance boosts I can get. – LazerSharks Nov 28 '14 at 21:36
  • I like this approach (using `emplace_back`) if the constructor of the class is trivial and do not use so many similar parameters and no defaults are specified. The reason for that is that if you change the constructor of the class to something that the old parameters are still type compatible, you potentially introduce bugs really hard to find. AFAIK not even Visual Assist (C++ refactoring tool) is able to locate and refactor constructor signatures in `emplace_back`, so be aware! – Darien Pardinas Nov 30 '14 at 21:46
1

Avoid generating an invalid state. If you're not setting the member pointers, then at least you must set them to nullptr. If possible, generate a useful and valid state as soon as possible (unless that is expensive, when instead you might want use lazy initialisation, but still a valid though incomplete initial state). Thus, if you can obtain the final correct values for the members, then set them immediately.

If those pointers own any memory (are initialised from freshly allocated memory), then you must be very careful. You should actually use std::unique_ptr<> in this case and move your object, or even better directly construct in its vector position via vector::emplace_back().

Also, if you know the total number of object in advance, you may want to vector::reserve() enough space prior to putting objects in the vector.

Walter
  • 44,150
  • 20
  • 113
  • 196
  • By freshly allocated memory, do you mean just point to something instantiated on the stack, on the heap, or both? Is this a good definition of lazy initialization? (simply waiting until last minute to initialize values) http://en.wikipedia.org/wiki/Lazy_initialization – LazerSharks Nov 28 '14 at 21:30