Delay proxy by overloading the arrow operator

07 Sep 2020 - John Z. Li

There is this class A defined as below:

class A {
public:
  A(std::string m_, int c_) : msg(m_), count(c_) {}
  void side_effect() {
    for (auto i = 0; i < count; ++i)
      std::cout << i << ": " << msg << std::endl;
  }

private:
  std::string msg;
  int count;
};

A has a public member function side_effect(), which is used to trigger some side effects. For this toy example, it does nothing but print a message for a given number of times to the terminal.

The problem at hand is that when A is tested, we want the side_effect() function performs its task in a controlled manner. Again, for this toy example, we would like to illustrate the point by asking the thread on which side_effect() is called to sleep for a pre-specified time interval before the function body is executed.

Of course, we don’t want the solution to be intrusive. That is, we don’t want to modify the class A itself. We also want the solution to be transparent, meaning that calling the function in our test code is done in the same fashion that A is used.

One way to achieve this to create a delay proxy class of class A. In the proxy class the arrow operator is overloaded to insert what we want to perform before the side effects are triggered. To make the delay proxy class relatively generate, we make it a class template, as below:

template <class T, int Pause, class... Args>
class delay_proxy {
public:
  delay_proxy(Args&&... args)
      :  up(std::make_unique<T>(std::forward<Args>(args)...))
        {}

  T* operator->() {
    std::this_thread::sleep_for(std::chrono::milliseconds(Pause));
    return up.get();
  }

private:
  std::unique_ptr<T> up;
};

What happens here is that the delay_proxy class owns a unique_ptr to an instance of the test class; when an instance of the delay_proxy class is constructed, parameters are forwarded to the constructor of the tested class, and configurable parameters are also set. Then, one can just test class A like

delay_proxy<A, 100, std::string, int> dp("this is a test", 10);
dp->side_effect();

We can see that we are using dp as if it is an instance of class A, because the arrow operator “falls through” along the calling chain, eventually resolving to a normal member function call after satisfying “pre-conditions” defined in the function body of the arrow operator in class delay_proxy.