Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Let's say I have two object types, A and B. The relationship between them is many-to-many, but neither of them is the owner of the other.

Both A and B instances need to be aware of the connection; it's not just one way.

So, we can do this:

class A
{
    ...

    private: std::vector<B *> Bs;
}

class B
{
    private: std::vector<A *> As;
}

My question is: where do I put the functions to create and destroy the connections?

Should it be A::Attach(B), which then updates A::Bs and B::As vectors?

Or should it be B::Attach(A), which seems equally reasonable.

Neither of those feels right. If I stop working with the code, and come back after a week, I'm sure I won't be able to recall if I should be doing A.Attach(B) or B.Attach(A).

Perhaps it should be a function like this:

CreateConnection(A, B);

But making a global function seems undesirable also, given that it's a function specifically for working with only classes A and B.

Another question: if I run into this problem/requirement often, can I somehow make a general solution for it? Perhaps a TwoWayConnection class that I can derive from or use within classes that share this type of relationship?

What are some good ways to handle this situation... I know how to handle the one-to-many "C owns D" situation quite well, but this one is trickier.

Edit: Just to make it more explicit, this question doesn't involve ownership issues. Both A and B are owned by some other object Z, and Z takes care of all ownership issues. I'm only interested in how to create/remove the many-to-many links between A and B.

share|improve this question
    
I would create a Z::Attach(A, B), so, if Z owns the two kinds of objects, "feels right", for him to create the connection. In my (not-so-good) example, Z would be a repository for either A and B. I can't see another way without a pratical example. –  Machado May 10 '12 at 21:10
    
To be more precise, A and B may have different owners. Perhaps A is owned by Z, whereas B is owned by X, which is in turn owned by Z. As you can see, it then becomes really convoluted trying to manage the link elsewhere. A and B need to be able to quickly access a list of the pairs it's linked to. –  Dmitri Shuralyov May 10 '12 at 21:14
    
If you're curious about the real names of A and B in my situation, they are Pointer and GestureRecognizer. Pointers are owned and managed by the InputManager class. GestureRecognizers are owned by Widget instances, which are in turned owned by a Screen instance which is owned by an App instance. Pointers get assigned to GestureRecognizers so that they can feed raw input data to them, but GestureRecognizers need to be aware of how many pointers there are currently associated with them (to distinguish 1 finger vs. 2 finger gestures, etc.). –  Dmitri Shuralyov May 10 '12 at 21:24
    
Now that I think about that more, it seems that I'm just trying to do frame-based (rather than pointer-based) gesture recognition. So I might as well just pass events to gesture frame-at-a-time rather than pointer-at-a-time. Hmm. –  Dmitri Shuralyov May 10 '12 at 21:36
add comment

5 Answers

up vote 2 down vote accepted

One way is to add a public Attach() method and also a protected AttachWithoutReciprocating() method to each class. Make A and B mutual friends so that their Attach() methods can call the other's AttachWithoutReciprocating():

A::Attach(B &b) {
    Bs.push_back(&b);
    b.AttachWithoutReciprocating(*this);
}

A::AttachWithoutReciprocating(B &b) {
    Bs.push_back(&b);
}

If you implement similar methods for B, you won't have to remember which class to call Attach() on.

I'm sure you could wrap that behavior up in a MutuallyAttachable class that both A and B inherit from, thus avoiding repeating yourself and scoring bonus points on Judgement Day. But even the unsophisticated implement-it-in-both-places approach will get the job done.

share|improve this answer
1  
This sounds precisely like the solution I was thinking of in the back of my mind (i.e. to put Attach() in both A and B). I also really like the more DRY solution (I really dislike duplicating code) of having a MutuallyAttachable class that you can derive from whenever this behaviour is needed (I foresee quite often). Just seeing the same solution offered by someone else makes me that much more confident in it, thanks! –  Dmitri Shuralyov May 10 '12 at 21:04
    
Accepting this because IMO it's the best solution offered so far. Thanks! I'm going to implement that MutuallyAttachable class now and report here if I run into any unforeseen issues (some difficulties may also arise due to the now-multiple inheritance which I don't have much experience with yet). –  Dmitri Shuralyov May 10 '12 at 21:16
    
Everything worked out smoothly. However, as per my last comment on the original question, I'm starting to realize I don't need this functionality for what I originally envisioned. Instead of keeping track of these 2-way connections, I will just pass every pair as a parameter to the function that needs it. However, this may still come in handy at a later time. –  Dmitri Shuralyov May 11 '12 at 14:40
    
Here's the MutuallyAttachable class I wrote for this task, if anyone wants to reuse it: goo.gl/VY9RB (Pastebin) Edit: This code could be improved by making it a template class. –  Dmitri Shuralyov May 11 '12 at 14:45
1  
I've placed the MutuallyAttachable<T, U> class template at Gist. gist.github.com/3308058 –  Dmitri Shuralyov Aug 9 '12 at 21:13
add comment

Usually when I get into situations like this that feel awkward, but I can't quite tell why, it's because I'm trying to put something in the wrong class. There's almost always a way to move some functionality out of class A and B to make things more clear.

One candidate for moving the association to is the code that creates the association in the first place. Maybe instead of calling attach() it simply stores a list of std::pair<A, B>. If there are multiple places creating associations it can be abstracted out into one class.

Another candidate for a move is the object that owns the associated objects, Z in your case.

Another common candidate for moving the association to is the code that is frequently calling methods on A or B objects. For example, instead of calling a->foo(), which internally does something with all associated B objects, it already knows the associations and calls a->foo(b) for each one. Again, if multiple places call it, it can be abstracted into a single class.

Lots of times the objects that create, own, and use the association happen to be the same object. That can make the refactoring very easy.

The last method is to derive the relationship from other relationships. For example, brothers and sisters are a many-to-many relationship, but rather than keeping a list of sisters in the brother class and vice versa, you derive that relationship from the parental relationship. The other advantage of this method is that it creates a single source of truth. There's no possible way for a bug or runtime error to create a discrepancy between the brother, sister, and parent lists, because you only have the one list.

This kind of refactoring isn't a magic formula that works every time. You still have to experiment until you find a good fit for each individual circumstance, but I've found it to work about 95% of the time. The remaining times you just have to live with the awkwardness.

share|improve this answer
add comment

Since you're putting C++11 on the candidate list... Take a look at smart pointers.
http://www2.research.att.com/~bs/C++0xFAQ.html#std-unique_ptr

They appear to provide more ways to tackle this exact problem.

Edit: A snippet of what I thought was relevant to the Q:

weak_ptr

Weak pointers are often explained as what you need to break loops in data structures managed using shared_ptrs. I think it is better to think of a weak_ptr as a pointer to something that you need access to (only) if it exists, and may get deleted (by someone else), and must have its destructor called after its last use (usually to delete anon-memory resource)
share|improve this answer
    
Or prior to C++11, you can use boost. I believe the standard smart pointers are based on the boost implementation. –  Steven Burnap May 10 '12 at 20:05
    
I'm aware of C++11 smart pointers. However, I'm not sure if they add anything to the question of what scope the function/code to create a new link in the many-to-many relationship should belong to. If I'm not mistaken, they solve a different problem, that is of ownership and destruction. –  Dmitri Shuralyov May 10 '12 at 20:14
2  
They do solve exactly a different problem. The OP explicitly states that neither of them is the owner of the other. –  DeadMG May 10 '12 at 20:25
    
I had envisioned using weak pointers so members of A and B could point at members of the other but also remain tolerant of those members dropping out. I haven't used weak pointers or boost, so I could easily be missing restrictions of those approaches. Bjarne's Asteroid description seemed reasonably close to the original question. Apologies if I'm way off. –  GlenH7 May 10 '12 at 20:48
add comment

Is it possible for the relationship itself to have additional properties?

If so, then it should be a separate class.

If not, then any reciprocating list-management mechanism will suffice.

share|improve this answer
    
If it's a separate class, how would A know its linked instances of B, and B know its linked instances of A? At that point, both A and B would need to have a 1-to-many relationship with C, wouldn't they? –  Dmitri Shuralyov May 11 '12 at 1:20
    
A and B would both need a reference to the collection of C instances; C serves the same purpose as a 'bridge table' in relational modeling –  Steven A. Lowe May 11 '12 at 17:09
add comment

If I were implementing this, I would put Attach in both A and B. Inside of the Attach method, I would then call Attach on the object passed in. That way you can call either A.Attach(B) or B.Attach(A). My syntax may not be correct, I haven't used c++ in years:

class A {
  private: std::vector<B *> Bs;

  void Attach (B* obj) {
    // Only add obj if it's not in the vector
    if (std::find(Bs.begin(), Bs.end(), obj) == Bs.end()) {
      //Add obj to the vector
      Bs.push_back(obj);

      // Attach this class to obj
      obj->Attach(this);
    }
  }    
}

class B {
  private: std::vector<A *> As;

  void Attach (A* obj) {
    // Only add obj if it's not in the vector
    if (std::find(As.begin(), As.end(), obj) == As.end()) {
      //Add obj to the vector
      As.push_back(obj);

      // Attach this class to obj
      obj->Attach(this);
    }
  }    
}

An alternate method would be to create a 3rd class, C which manages a static list of A-B pairings.

share|improve this answer
    
Just a question regarding your alternative suggestion of having a 3rd class C to manage a list of A-B pairs: How would A and B know its pair members? Would each A and B need a link to (single) C, then ask C to find the appropriate pairs? B::Foo() { std::vector<A *> As = C.GetAs(this); /* Do something with As... */ } Or did you envision something else? Because this seems awfully convoluted. –  Dmitri Shuralyov May 10 '12 at 21:06
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.