I've heard circular references are generally an issue, however I was wondering if this was true for interfaces that reference other interfaces, for example:

    IAnswer getCorrectAnswer();
    IList<IAnswer> getAllAnswers();

    IQuestion getQuestionInResponseTo();

Is there any case where this would be an issue? It doesn't seem to me to be resolveable if it were. My best guess is that this wouldn't be an issue as interfaces don't demand much in terms of referencing. Thanks in advance.

  • 1
    That's not a circular reference; it's a recursive reference. Perfectly valid. – Robert Harvey Apr 6 '18 at 15:35
  • 2
  • @RobertHarvey: Are you sure? It looks to me like the Question contains a list of answers, all of which in turn point back to the containing question (same instance). – Filip Milovanović Apr 6 '18 at 16:14
  • @OP: I see you've added the "dependency-injection" tag. Is your question primarily about dependency resolution? If so, you should probably edit the question to make that more explicit. – Filip Milovanović Apr 6 '18 at 16:14
  • 1
    @RobertHarvey, it's definitely a circular reference. IQuestion references the type IAnswer, and IAnswer references the type IQuestion. – Jesus Alonso Abad Apr 6 '18 at 16:59

It's an issue as long as you have to check the integrity/validity of both references as soon as one of them change (and might want to look out for possible endless recursion doing so). As they seem to be pretty static, it's not (much of) an issue; you'd probably need to double-check that during the construction of the model objects. But don't forget to do it. You don't want Question A referencing the Answer A, and Answer A referencing Question B as its "parent".

A bit off-topic: why keeping both references? Is it just to save some time to get the question that an answer belongs to? I think in the contexts you'd probably be using the question and its answers in, the context already has the references to both the question and the answers, so that relationship shouldn't be hard to extract from them, and would make your model design much less error-prone and easier to maintain.

| improve this answer | |

I don't think Its technically a circular reference. It will compile.

But there are some issues you should be aware of with this kind of structure. For example serialisation.

A naive approach could lead to an infinite loop if you refer back to the parent object.

Additionally you maybe have to account for a null value. If you have a tree of these objects the final leaf will have a null value.

I would avoid such structures outside of those specifically designed for a tree, linked list or recursion.

Where you just want an easy reference to the parent object I would rather loop through the parents to find it.

| improve this answer | |
  • +1 for mentioning serialization. – Laiv Apr 6 '18 at 17:27
  • But interfaces tell us nothing about the structure. You can loop through the parents to implement getQuestionInResponseTo(). The interface does not mandate that any particular references must be hold. I think that is what the OP means by "interfaces don't demand much in terms of referencing". – Stop harming Monica Apr 6 '18 at 19:33
  • That's true, it doesnt have to be a parent child relationship in the example. or at all really, but both points hold, you either have nulls or recursion at some point. accessing code needs some way of telling where to stop – Ewan Apr 6 '18 at 19:48

It is clearly a circular reference at the level of the interfaces. Similar to recursive data types — which have circular references at the type level — that does not necessarily mean that the instance objects are themselves are engaged in circular references (though they certainly could be).

In many circumstances (but certainly not all), there is nothing "wrong" with circular references, even though they will generally exhibit some construction & destruction ordering issues (i.e. precluding some of the instance objects involved from being immutable objects).

It can help to have a notion of parent and child.  While the parent references the children, and the child reference the parent, we usually agree that deleting a child deletes only (the child itself and) the one parent-child reference (and not the parent itself), whereas deleting the parent can mean deleting not just the parent-child references, but also all its current children as well.

This means that the parent & child references are directional and asymmetrical, and in some sense, one is effectively "stronger" than the other despite the circularity, as one includes a notion of ownership that other does not.

While some databases allow us to specify this "strength" as an expression of constraint; our programming languages, by contrast, mostly do not allow us to differentiate between parent-to-child and child-to-parent references (they are both merely references).  If they did allow such differentiation, we could consider parent/child and child/parent references as non-circular in the light of their asymmetrical nature.

Some reference counting algorithms break cycles via implicitly tagged or explicitly declared strong references.

In the small, cycles are not too bad when using a garbage collected language.  For manual memory management, however, especially reference counting, cycles are an issue.

In the large, such as when we get to cycles in libraries (one library depends on another which depends on it...), these can be problematic in particular in regards to initialization ordering.  Such problems tend to be exacerbated for circularities among operating systems components like drivers and/or subsystems: not that it cannot be done but it increases the complexity, while also hampering the ability for subsetting: creating a smaller version of the operating system for a smaller environment.

| improve this answer | |

It's only an issue if you're using a language where circular references can cause memory-leaks (like C++) or if you're doing something such as loading these objects from a database and end up in an infinite loop if your ORM is not aware of how to handle this properly. All the issues I'm aware of can be worked around as long as you are aware of them, using things like weak/non-owning pointers or lazy initialisation.

| improve this answer | |
  • 3
    There's nothing about the C++ language that inhibits you from creating cyclic, linked data structures. You might not want to use std::shared_ptr to implement that, but std::shared_ptr is not part of the C++ language. It's part of the C++ standard library, and nobody's going to force you to use it in places where it isn't helpful. – Solomon Slow Apr 6 '18 at 16:46
  • @jameslarge, well, it's not inhibiting, but it's an issue ;) – Jesus Alonso Abad Apr 6 '18 at 16:58
  • @jameslarge Using shared_ptr prevents more memory-leaks than it causes, so I wouldn't suggest avoiding it completely. If you're writing modern C++ you should be using shared_ptr, you just have to remember to also use weak_ptr where using only shared_ptr might cause a circular reference. – Sean Burton Apr 9 '18 at 9:31
  • I did not mean to suggest that std::shared_ptr is a bad thing. I use it in my own code. – Solomon Slow Apr 9 '18 at 15:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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