Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Long delegation chains in C++

This is definitely subjective, but I'd like to try to avoid it becoming argumentative. I think it could be an interesting question if people treat it appropriately.

In my several recent projects I used to implement architectures where long delegation chains are a common thing.

Dual delegation chains can be encountered very often:

bool Exists = Env->FileSystem->FileExists( "foo.txt" );

And triple delegation is not rare at all:

Env->Renderer->GetCanvas()->TextStr( ... );

Delegation chains of higher order exist but are really scarce.

In above mentioned examples no NULL run-time checks are performed since the objects used are always there and are vital to the functioning of the program and explicitly constructed when execution starts. Basically I used to split a delegation chain in these cases:

1) I reuse the object obtained through a delegation chain:

{ // make C invisible to the parent scope
   clCanvas* C = Env->Renderer->GetCanvas();
   C->TextStr( ... );
   C->TextStr( ... );
   C->TextStr( ... );
}

2) An intermediate object somewhere in the middle of the delegation chain should be checked for NULL before usage. Eg.

clCanvas* C = Env->Renderer->GetCanvas();

if ( C ) C->TextStr( ... );

I used to fight the case (2) by providing proxy objects so that a method can be invoked on non-NULL object leading to an empty result.

My questions are:

  1. Is either of cases (1) or (2) a pattern or an antipattern?
  2. Is there a better way to deal with long delegation chains in C++?

Here are some pros and cons I considered while making my choice:

Pros:

  • it is very descriptive: it is clear out of 1 line of code where did the object came from
  • long delegation chains look nice

Cons:

  • interactive debugging is labored since it is hard to inspect more than one temporary object in the delegation chain

I would like to know other pros and cons of the long delegation chains. Please, present your reasoning and vote based on how well-argued opinion is and not how well you agree with it.

like image 901
Sergey K. Avatar asked Jul 24 '12 13:07

Sergey K.


3 Answers

I wouldn't go so far to call either an anti-pattern. However, the first has the disadvantage that your variable C is visible even after it's logically relevant (too gratuitous scoping).

You can get around this by using this syntax:

if (clCanvas* C = Env->Renderer->GetCanvas()) {
  C->TextStr( ... );
  /* some more things with C */
}

This is allowed in C++ (while it's not in C) and allows you to keep proper scope (C is scoped as if it were inside the conditional's block) and check for NULL.

Asserting that something is not NULL is by all means better than getting killed by a SegFault. So I wouldn't recommend simply skipping these checks, unless you're a 100% sure that that pointer can never ever be NULL.


Additionally, you could encapsulate your checks in an extra free function, if you feel particularly dandy:

template <typename T>
T notNULL(T value) {
  assert(value);
  return value;
}

// e.g.
notNULL(notNULL(Env)->Renderer->GetCanvas())->TextStr();
like image 163
bitmask Avatar answered Oct 22 '22 16:10

bitmask


In my experience, chains like that often contains getters that are less than trivial, leading to inefficiencies. I think that (1) is a reasonable approach. Using proxy objects seems like an overkill. I would rather see a crash on a NULL pointer rather than using a proxy objects.

like image 6
wilx Avatar answered Oct 22 '22 17:10

wilx


Such long chain of delegation should not happens if you follow the Law of Demeter. I've often argued with some of its proponents that they where holding themselves to it too conscientiously, but if you come to the point to wonder how best to handle long delegation chains, you should probably be a little more compliant with its recommendations.

like image 6
AProgrammer Avatar answered Oct 22 '22 15:10

AProgrammer