Function Objects

One of the most powerful features of C++ is function overloading. As we have seen, one can essentially create new languages embedded within C++ by appropriately overloading functions and operators. Overloading is particular interesting. Although we have overloaded it for matrix and vector types to be used as indexing, can be defined to take any kind of arguments. In essence, overloaded allows you to pass arbitrary parameters to a function that you define – in essence letting you build your own functions.

Now, of course, when you write a function down when you are programming, you are building functions. But when you build an object that has an overloaded , your program can generate new functions. This is similar to the concept of lambda described below (and in fact was leveraged to develop the original Boost.Lambda library).

Objects that have an operator() defined are called function objects. What makes function objects so powerful is that they can be used just like a function in your programs. The syntax in C++ for a function call is a name followed by a parameter list enclosed in parentheses. This same syntax can mean a plain function call – or a call to an object’s operator() member function.

We have seen two cases already where we pass functions as parameters: std::thread and std::async. But what is important in those cases is not that an actual function is being passed to std::thread and std::async, but rather that the thing being passed is callable (i.e., that it can be passed parameters and a function run on those parameters).

Let’s look at an example. In the running Bonnie and Clyde bank deposit and withdrawal program, we have had two functions withdraw and deposit that are invoked asynchronously by the main program. Instead of defining withdraw and depositas functions, let’s instead define them as function objects.

Let’s start with a transaction class.

int bank_balance = 300;

class transaction {
public:
  transaction(int _sign, int& _balance) :
    sign(_sign), balance(_balance) {}

  void operator()(int amount) {
    std::lock_guard<std::mutex> tr_lock(tr_mutex);
    balance += sign*amount;
  }

  // Overload operator() to be "noisy"
  void operator()(const std::string& msg, int amount) {
    std::lock_guard<std::mutex> tr_lock(tr_mutex);
    std::cout << msg << ": " << sign*amount;
    std::cout << " -- Balance: " << balance << std::endl;
    balance += sign*amount;
  }

private:
  int sign;
  int &balance;
  static std::mutex tr_mutex;     // Note static!
};
std::mutex transaction::tr_mutex; // construct static member

The constructor for this class takes a sign (which should be plus or minus one and keeps a reference to a balance value. It also has its own mutex for safely modifying the balance.

Previously, the function was defined and invoked like this

void withdraw(int amount) {
  bank_balance -= amount;
}

int main() {

  withdraw(100);

  return 0;
}

With the transaction class we can create a function object that can be used exactly the same way:

int main() {
  transaction withdraw(-1, balance);

  withdraw(100);

  return 0;
}

Again, note that we invoke withdraw the same way in both cases – yet in the first case withdraw is the function withdraw and in the second case it is an object of class transaction.

The full Bonnie and Clyde example (with threads, say) would then be:

int bank_balance = 300;

int main() {
  transaction deposit ( 1, balance);  // define an object named deposit
  transaction withdraw(-1, balance);  // define an object named withdraw

  cout << "Starting balance is " << bank_balance << endl;

  thread bonnie(withdraw, "Bonnie", 100);
  thread clyde(deposit, "Clyde", 100);

  bonnie.join();
  clyde.join();

  cout << "Final bank balance is " << bank_balance << endl;

  return 0;
}

(For brevity we omit the messages that were printed by the original functions). This looks very much like the original program where and were functions. But remember, in this case, they are objects. And they are created at run time when we construct them. In summary, function objects let you dynamically create and use stateful objects, just as if they were statically defined functions.

Read through the code for this example and run it (and/or modify it).