Reputation: 1779
I am currently manually managing the lifecycle of objects in my project. I am considering switching to smart pointers, specifically tr1::shared_pointer and tr1::weak_ptr. However, I am seeing a couple of problems and would like to get some input on best practices.
Consider the following class diagram:
In this diagram, the thick arrows represent associations with an ownership semantics (the source is responsible for deleting the target or targets). The thin arrows represent associations without ownership.
From what I understand, one way of implementing an association with ownership semantics would be to use tr1::shared_ptr (or a collection thereof). Other associations can be implemented using either tr1::shared_ptr or tr1::weak_ptr. The former is prohibited if it may result in circular references because that would prevent proper release of resources.
As you can see, there is a circle of associations between classes Edge and Side. I can easily break this by implementing the "left" and "right" associations from Edge to Side using tr1::weak_ptrs. However, I would prefer using the smart pointers to document, in code, the ownership semantics of the associations. So I would like to use shared_ptrs only for the associations represented by thick arrows in the diagram, and weak_ptrs for everything else.
Now my first question is: Should I use weak_ptrs liberally as described above, or should I use them as little as possible (only to avoid circular references)?
The next question is: Suppose I have a function that computes the average of a set of vertices. Suppose further that I have implemented the association from the Polyhedron to its Vertices like so:
class Vertex;
class Polyhedron {
protected:
std::vector<std::tr1::shared_ptr<Vertex> > m_vertices;
};
and that I have implemented the association from a Side to its Vertices like so:
class Vertex;
class Side {
protected:
std::vector<std::tr1::weak_ptr<Vertex> > m_vertices;
};
Note that the set of vertices of a Side is a subset of the set of vertices of a Polyhedron. Now let's say I have a function that computes the average of a set of vertices. The function is currently declared like so:
const Vertex centerOfVertices(std::vector<Vertex*> vertices);
Now if I represent the associations as above, I suddenly need two functions if I understand everything correctly:
const Vertex centerOfVertices(std::vector<std::tr1::shared_ptr<Vertex> > vertices);
const Vertex centerOfVertices(std::vector<std::tr1::weak_ptr<Vertex> > vertices);
Because I cannot convert between a vector of shared_ptr and a vector of weak_ptr. This smells funny to me. I would like to know what approach I should take here to avoid such a situation.
Upvotes: 4
Views: 362
Reputation: 157344
Using shared pointers seems sensible, especially as you may find that vertices etc. can be shared between polyhedra.
To convert from a vector of weak pointers to a vector of shared pointers, use an explicit constructor:
centerOfVertices(std::vector<std::tr1::shared_ptr<Vertex> >(vertices.begin(), vertices.end()));
Upvotes: 1
Reputation: 40058
You certainly can also use shared_ptr for cyclic references. There are some rare situations where this actually makes sense. However, you must take care to break the cycle once you are done with the objects.
Upvotes: 0
Reputation: 545588
However, I would prefer using the smart pointers to document, in code, the ownership semantics of the associations.
As you should. That’s what they express perfectly, after all.
So I would like to use shared_ptrs only for the associations represented by thick arrows in the diagram, and weak_ptrs for everything else.
Go for it, with one caveat: your ownership doesn’t look at all like shared ownership, it’s simple, unique ownership. Consequently, the appropriate smart pointer here isn’t shared_ptr
but rather std::unique_ptr
, and the weak pointers would then simply be raw pointers.
Now if I represent the associations as above, I suddenly need two functions …
Yes. Well, or you use templates.
Alternatively, since the function isn’t actually interested in sharing ownership at all, you could pass raw pointers to it, but this would of course imply first getting raw pointers from your structure, which requires an added step for the client, which may not be good for the interface.
Upvotes: 1