Ikaros
Ikaros

Reputation: 395

Detecting duplicate work at compile time within C++ code

lets consider the following code:

struct Foo {
    Foo(Bar b1, Bar b2) : b1(b1), b2(b2) {}
    void work() {
         b1.work();
         b2.work();
         //something
    }
    Bar& b1;
    Bar& b2;
};

struct Bar {
    void work() { /* something */ }
};

int main() {
    Bar a, b;
    a.work();
    //something...
    Foo c(a,b);
    c.work();
    //something...
}

The way I wrote that (or intended to write it), a.work() will get executed twice. But let's say, I, as the programmer know, that executing it twice is a waste of execution time and let's say this was part of a far more complex piece of software where it would be far too troublesome to keep track manually what work is and isn't done.

Obviously I could store some boolean flag in Bar and check every single time whether the work has been done already, but I want to know, if there is some way where I can already catch that at compile time. Because at compile time it is already clear that the work had been done.

Upvotes: 3

Views: 101

Answers (3)

arsdever
arsdever

Reputation: 1262

Actually, at compile time it isn't obvious, whether the code was executed or not. Suppose you have an if statement and within it, you call a.work(). How does the compiler know, whether at that time the a.work() was executed? As you say, don't think, that the if statement is very simple (suppose, it is looking for some external signal and executes the code depending on that signal). The best way to avoid the behavior is to keep a boolean.

Upvotes: 1

arsdever
arsdever

Reputation: 1262

Another approach. Have a function pointer within the Bar object and in work() function call the pointer. In the constructor, define the pointer to be the actual work function. At the end of the function, reassign the pointer to be an empty function.

In this case, the first execution will do the job. But later executions will do nothing (also not checking the boolean flag)

struct Bar {
  typedef void (*Bar::fptr_t)();
  Bar() : fptr(actual_work) {}
  void actual_work() {
    /*something*/;
    fptr = &Bar::empty_work;
  }

  void empty_work() {}
  void work() {fptr();}
  fptr_t fptr;
};

Something like above.

Upvotes: 2

Asteroids With Wings
Asteroids With Wings

Reputation: 17464

No, not really.

The compiler is capable of some static analysis, and if you could ask it to diagnose this condition, it may be able to do so in some simple cases. But as soon as you have a non-trivial flow (runtime if conditions, for example), that goes out of the window very quickly. That's probably part of the reason that nobody has created such a programmable feature for compilers: high complexity, with negligible utility.

It may be possible to program some third-party static analysers (or create one!) to diagnose your simple case, but again that's a lot of work for handling only the most trivial cases that you can already spot with your eyes.

Instead, you could make work() happen in the Bar constructor. Then it's impossible to do the work twice on the same object. However, performing large quantities of work in a constructor is often frowned upon.

I would indeed keep a state flag within Bar, and return false from a subsequent work(), by maintaining the value of that flag accordingly. As a bonus, stick an assertion in the function before returning false so that you catch violations during your testing.

The state flag doesn't have to be a boolean; it can be an enum. Robust state machines inside your objects can be very helpful.

That being said, I'd advise revisiting your current approach where you pass references to things into other things that do work on them; it's not an easy-to-follow design, and this is only a simple example of your design! You may wish to consider passing some single-use proxy type instead.

Upvotes: 1

Related Questions