1

In the code below, there is a Grid which contains points. Likewise, Element and Face also have points but I want to them to point to points in Grid.

Should I use smart or raw pointers. If I use smart pointers should I use std::unique_ptr or std::shared_ptr?

struct Vector3
{
    vector <double> dim;

    Vector3 ()
    {
        dim.resize(3);
    }
};

struct Element
{
    vector <Vector3*> points;
};

struct Face
{
    vector <Vector3*> points;
};

struct Grid
{
    vector <Vector3> points;

    vector <Element> elms;
    vector <Face> faces;
};    
Shoe
  • 74,840
  • 36
  • 166
  • 272
Shibli
  • 5,879
  • 13
  • 62
  • 126
  • 2
    It depends >:] e.g. What is the lifetime of `Grid` vs `Element`? Are the `points` removed from `Grid` at some time, but need still be accessible from `Element`? etc. – dyp Jun 04 '14 at 23:46
  • Also, whether the Grid's vector gets resized. – Pete Kirkham Jun 04 '14 at 23:47
  • What does Vector3 contain? – balki Jun 04 '14 at 23:47
  • @dyp: `Grid` may be deleted during the execution of the program. – Shibli Jun 04 '14 at 23:47
  • @PeteKirkham: I read the size of `Grid.points` from data file. – Shibli Jun 04 '14 at 23:48
  • Well, yes, but are there still `Element`s around which contain pointers to the points of the `Grid` object when the latter is destroyed? Do those `Element`s still need access to the points? – dyp Jun 04 '14 at 23:49
  • Apologies, made a mistake. Please see the edit. – Shibli Jun 04 '14 at 23:54
  • Please also [refer here](http://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one?rq=1) for more informations. – Shoe Jun 04 '14 at 23:58
  • Ah, that shows the design better. Can we assume that `Element` and `Face` objects are *only* created as elements of `elms` and `faces` (i.e., inside `Grid`), or as temporaries (i.e. do not outlive a `Grid` object whose `points` they point to)? – dyp Jun 04 '14 at 23:59
  • Is that really a duplicate? Seems it's the wrong answer... – Deduplicator Jun 05 '14 at 00:01
  • @Deduplicator what do you mean? – Shoe Jun 05 '14 at 00:01
  • @dyp: Yes, exactly what you said. – Shibli Jun 05 '14 at 00:02
  • @Jefffrey Aren't indices at least as good, if not better? Can even be serialized fast, if that's a consideration. Can also be memory-mapped without change, directly from the data-file. – Deduplicator Jun 05 '14 at 00:02
  • @Deduplicator I don't understand what you mean by indeces but he should probably consider an `std::smart_pointer` is the resource is shared between the 3 classes. Otherwise an `std::reference_wrapper` is a good idea as well (depending on the preconditions of the lifetime of `Grid`'s points. In any way to the question "Should I use smart or raw pointers. If I use smart pointers should I use std::unique_ptr or std::shared_ptr?" the answer is in both links I've posted. – Shoe Jun 05 '14 at 00:07
  • @Deduplicator The [second one](http://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one?rq=1) is more recent, but with a little bit of research you can easily understand the first one. – Shoe Jun 05 '14 at 00:08
  • His `Face`s and `Element`s only ever refer to `Vector3`s in the owning `Grid` vector. Thus, indices into the `Grid`s vector of all `Vector3`s are a natural type to use. – Deduplicator Jun 05 '14 at 00:09
  • @Deduplicator except when you modify the vector. Then the indexes may be invalidated, while an `std::shared_ptr` is not. – Shoe Jun 05 '14 at 00:12
  • @Jefffrey: Indices are never invalidated, unless the element is removed from the container. Iterators are, and pointers. – Deduplicator Jun 05 '14 at 00:13
  • @Deduplicator Modifying the vector includes the case in which elements are deleted, then it's trivial to understand that positions are shifted and indices are invalidated in the sense that they do not refer to the same element anymore. In an `std::vector>`, the pointers to the resources (contained in the shared pointer) are never invalidated, only the iterators to the element (and only if that operation causes a reallocation). – Shoe Jun 05 '14 at 00:17
  • @Jefffrey: Sorry, but that does not match with the OPs problem description. – Deduplicator Jun 05 '14 at 00:20
  • @Deduplicator what doesn't match with the problem description? – Shoe Jun 05 '14 at 00:22
  • @Jefffrey Arbitrary removals of points from the grid. Actually, any change of those points. OP has not added that to the question itself though, only mentioned it in the comments yet, after prompting. – Deduplicator Jun 05 '14 at 00:24
  • @Deduplicator Oh, I think I understand what you mean. I've reopened it and posted and answer. – Shoe Jun 05 '14 at 00:46

1 Answers1

3

Here you specified that the precondition is that Element and Face objects are created in a Grid object with the elements of the container referring to the same Grid containers, therefore the lifetime of all three containers (points, elms and faces) is the same.

Now you have to consider two cases.

Semi-immutable points

In this case, points is guaranteed to never invalidate references to its elements (eg. it's never modified). Here you don't need any smart pointers, you can just use a simple std::reference_wrapper as follows:

struct Vector3
{
    std::vector<double> dim;
    Vector3 () : dim(3) {}
};

template<class Type>
using ref_vec = std::vector<std::reference_wrapper<Type>>;

struct Element { ref_vec<Vector3> points; };
struct Face    { ref_vec<Vector3> points; };

struct Grid
{
    std::vector<Vector3>  points;
    std::vector<Element>  elms;
    std::vector<Face>     faces;
};

Another solution, non equivalent to your example (elms and faces don't have direct access to the Vector3 object) might be to use indexes:

struct Vector3
{
    std::vector<double> dim;
    Vector3 () : dim(3) {}
};

struct Grid
{
    struct Element { std::size_t point_indices; };
    struct Face    { std::size_t point_indices; };

    std::vector<Vector3>  points;
    std::vector<Element>  elms;
    std::vector<Face>     faces;
};

That is, you store the indices of points.

Mutable points

If the operations performed on points can invalidate references, then you might want to consider another container that does not invalidate references/pointers/iterators to the element.

For example std::deque guarantees the validity of references for deletion/insertion at the beginning and end of the container.

Once you have chosen the correct container you can just apply the same ideas as above.

Community
  • 1
  • 1
Shoe
  • 74,840
  • 36
  • 166
  • 272
  • Will the first case compile since `reference_wrapper` has no default constructor? – Shibli Jun 05 '14 at 21:39
  • 1
    @Shibli, [It definitely will](http://coliru.stacked-crooked.com/a/28386ea7edba69ef). No `std::reference_wrapper` object is created. – Shoe Jun 05 '14 at 22:52