inline methods and code readability in modern C++

366 Views Asked by At

Alright, so sometimes my "coding brain" skips a gear; once in a while you can hear the gears grind. (For instance, every once in a while I write class Foo : Bar {} before reminding myself that's not proper anymore -- and hasn't been in a long time).

My current MO is to use inline methods as a way to improve code legibility and maintainability without sacrificing speed, but I ran across an issue recently that made me question this practice.

So, given (the admittedly contrived) code like:

double a;
double b = 0.0;
double c = 0.0;
...
// do some stuff here
...
// skip the sanity checks
// Magic Formula. This does what?
a = b + c - (b * c); 
...

I will write:

double a;
double b = 0.0;
double c = 0.0;
...
// do some stuff here
...
// skip the sanity checks
// Oh! It's probability!
a = ProbabilisticOr(b, c);
...
inline double ProbabilisticOr(double b, double c)
{
   // Skip the Sanity checks
   return b + c - (b * c);
}

The math I'm working on right now is fairly complex. If I want a generic CS/CE to be able to maintain it, it has to be written more like the second. The code is also pretty time sensitive.

I've recently run across an issue, as I said above. I made my mathematical constants static const double ... like a good little programmer; but when trying to access them inline the compiler bombs out for DLLs. The target OS is Linux, but I'm developing on Windows (Visual Studio 2013) and would like to keep it "cross-platform safe".

The solution to this little problem is to take them out-of-line; but, will that hurt my performance? Given the esoteric math involved, readability is a serious issue; but it still has to perform well.

Update:

To clarify, using more / different -- and much more contrived -- code:

#ifndef BUILD_DLL
#  define DLL_MODE __declspec(dllimport)
#else
#  define DLL_MODE __declspec(dllexport)
#endif


class DLL_MODE ContrivedProbabilityExample
{
public:
   inline ContrivedProbabilityExample(double value);
   inline ContrivedProbabilityExample& operator+=(double value);
private:
   inline void CheckValue(double value);
private:
   static const double ZERO_PROB = 0.0;
   static const double GUARANTEED_PROB= 1.0;
   double probability;

private:
   // Not implemented
   ContrivedProbabilityExample();
};

inline ContrivedProbabilityExample::ContrivedProbabilityExample(double value) : probability(value) 
{
   CheckValue(value);
}

inline ContrivedProbabilityExample& ContrivedProbabilityExample::operator+=(double value)
{
   CheckValue(value);
   probability = probability + value - (probability * value);
}

inline void ContrivedProbabilityExample::CheckValue(double value)
{
   if(value < ZERO_PROB || value > GUARANTEED_PROB) 
      throw std::range_error("Hey, whattaya think you're doing?");
}

This code will work fine in Static on both platforms; it will work as a Shared library on Linux. It will give an error under Windows when trying to use it as a DLL. The only solution is to move the CheckValue method out-of-line.

"old-school" inline had the CheckValue method's code being substituted "as-is" where it was called from; apparently "new-school" inline does ... nothing? (Since the compiler apparently does what it wants regardless.)

AFIK The only way to make this work under DLL is to move CheckValue out-of-line ... which could be a problem for time-sensitive code "old-school" (every call is/was guaranteed function overhead). Is this still a problem? Is there a "better" way to keep this readable; e.g. to not assume that every CS/CE working on my code will be proficient in Statistics?

Note: This is cross-platform, so "the compiler" may not be a meaningful phrase.

3

There are 3 best solutions below

0
Paul Evans On

You want to use constexp, something like:

inline constexpr double ProbabilisticOr(double b, double c)
{
    // Skip the Sanity checks
    return b + c - (b * c);
}

then you're free to do things like:

static const double a = ProbabilisticOr(b, c);
0
Danvil On

Not sure what your question is, but what about this:

class DLL_MODE ContrivedProbabilityExample
{
public:
   ContrivedProbabilityExample(double value)
   { CheckValue(value); }

   ContrivedProbabilityExample& operator+=(double value)
   {
       CheckValue(value);
       probability = probability + value - (probability * value);
   }

private:
   void CheckValue(double value)
   {
       if(value < ZERO_PROB || value > GUARANTEED_PROB) 
           throw std::range_error("Hey, whattaya think you're doing?");
   }

private:
   constexpr double ZERO_PROB = 0.0;
   constexpr double GUARANTEED_PROB= 1.0;
   double probability;

private:
   // Not implemented
   ContrivedProbabilityExample();
};
0
cmaster - reinstate monica On

If your problem is ensuring performance, the best way to handle this is to insert an assert() to perform the sanity check.

It should be up to the caller to ensure that proper values are passed to your probability functions (and should be well documented), and an assert() will help you debugging if some caller doesn't. However, when you ship your code, you can simply deactivate all asserts, removing the performance penalty of the check.

There is nothing faster than not checking at all, that's why dereferencing pointers in C/C++ has never been safe and never will.