10

Every time I have some functionality which is in the direction of "utility", I end up wondering which option is the best. For instance, printing message structs (own or external), some encoding/decoding code or simply a few useful conversion functions in the context I'm working.

The options I think about are:

1) Static function in helper class/struct.

struct helper
{
    static bool doSomething(...);
};

2) Nonmember function.

namespace helper
{
    bool doSomething(...);
}

3) Static nonmember function.

namespace helper
{
    static bool doSomething(...);
}

In some cases there might be necessary to initialize or keep state in the "utility", so then I go for option 1 to avoid "global" state. However, if there is no state that needs to be kept, should I then option 2 or 3? What's the practical difference between option 2 and 3?

What is important to consider and is there a preferred way to approach this? Thanks!

murrekatt
  • 5,525
  • 4
  • 34
  • 61
  • Option (3) just makes the function local to the translation unit, which may not be very useful, since you could essentially only use the function inside the TU that defines it. [See also this question](http://stackoverflow.com/questions/6034654/static-keyword-useless-in-namespace-scope). – Kerrek SB Aug 05 '11 at 08:13

3 Answers3

13

The difference between options 2 and 3 is that in the second case, the function will be internal to the translation unit. If the function is only defined in the cpp this should be the option (which is roughly equivalent to an unnamed namespace --which is a fourth option to consider, again roughly equivalent to 3).

If the function is to be used by different translation units then you should go with option 2. The function will be compiled once (unless you mark it as inline and provide the definition in the header), while with option 3 the compiler will create it's an internal copy in each translation unit.

As of option 1, I would avoid it. In Java or C# you are forced to use classes everywhere, and you end up with utility classes when operations do not nicely map to the object paradigm. In C++ on the other hand, you can provide those operations as free standing functions, and there is no need to add the extra layer. If you opt for the utility class, don't forget to disable the creation of the objects.

Whether the functions are at class or namespace level will affect the lookup, and that will impact on user code. Static member functions need to be qualified with the class name always, unless you are inside the class scope, while there are different ways of bringing namespace functions into scope. As an illustrative example, consider a bunch of math helper functions, and calling code:

double do_maths( double x ) {
   using namespace math;
   return my_sqrt( x ) * cube_root(x);
}
// alternatively with an utility class:
double do_maths( double x ) {
   return math::my_sqrt(x) * math::cube_root(x);
}

Which one you find easier to read is a different story, but I prefer the former: within the function I can select the namespace and then just focus on the operations and ignore lookup issues.

David Rodríguez - dribeas
  • 192,922
  • 20
  • 275
  • 473
  • +1 for this >> *but I prefer the former: within the function I can select the namespace and **then just focus on the operations and ignore lookup issues.*** << Important point! – Nawaz Aug 05 '11 at 08:24
10

Don't use classes as namespaces. Using a free (ie. nonmember) function inside a namespace is the simplest thing.

Moreover, the function shouldn't be static if it has to be used across multiple translation units. Otherwise, it will be duplicated.

Using classes as namespace is stupid: why make a class which you won't instantiate ?

If some state is to be kept, then have a global state somewhere: static variable inside the function, utility global object (possibly in a "private" namespace, or as a static global variable in one of your translation units). Don't write classes you don't instantiate.

Alas, people with a C#/Java background are used to do this stupid thing, but it is because their language designers have decided unilaterally that free functions are evil. Whether they should be shot or not is a matter of religion.

As a last word of caution: global state should be rarely used. Indeed, it couples your code to the existence of the global state in a way you don't control when the code grows. You should always ask yourself why you don't make this coupling explicit.

Alexandre C.
  • 52,206
  • 8
  • 116
  • 189
  • Sure. It's still an alternative and especially if there is state to be kept. – murrekatt Aug 05 '11 at 08:11
  • 1
    +1 for this >> `Whether they should be shot is a matter of religion.` :D – Nawaz Aug 05 '11 at 08:12
  • Reading your answer it looks like I pressed a button. – murrekatt Aug 05 '11 at 08:20
  • 1
    @murrekatt: A static member variable is just as global as any other global. You can control accessibility to the state, but that can be achieved in different ways, as for example enclosing the functions and the state in it's own translation unit, with a header that only offers the operations and the variables declared internally `static` or within an *unnamed namespace*. This has the advantage of truly hiding the implementation details: if you change those variables you will only need to compile your own translation unit, as they are not seen/included anywhere else. – David Rodríguez - dribeas Aug 05 '11 at 08:25
-1

I use struct with static functions, because it offers a slighly better isolation than non-member functions in namespaces (due to the avoidance of Koenig lookup).

To give an example of the thing I want to avoid:

namespace Foo
{
      struct A
      {
      };

      void f(const A& a) {}
}

void f(const Foo:A& a) { std::cout << "AAA"; }

int main(void)
{
      Foo::A a;
      f(a); // calls Foo:f
      return 0;
}
quant_dev
  • 5,983
  • 1
  • 31
  • 53
  • 1
    I use free functions *because* Koenig lookup exists. – Alexandre C. Aug 05 '11 at 08:41
  • Depends on what you want to do. – quant_dev Aug 05 '11 at 08:51
  • I'm ready to accept your point if you can provide me with an example where 1) Koenig lookup harms you 2) the fact that Koenig lookup may harm you is not a design smell. I fail to see where the problem is: after all, Koenig lookup is usually an advantage. – Alexandre C. Aug 05 '11 at 09:06
  • Well, I'd have to be bothered to care about your readiness to do this or that. Which I'm not ;-) – quant_dev Aug 05 '11 at 09:47
  • 1
    this was just a suggestion to enhance your terse statement. As to your actual example, by writing `void f(const A& a)`, you acknowledge that `f` is a part of `A`'s interface, and I see *as a feature* the fact that `Foo::f` is called instead of `f` from the global namespace (you don't put anything in the global namespace, do you ?). I fail to see where a potential ambiguity should reside. – Alexandre C. Aug 05 '11 at 10:19