Binders and wrappers for callable objects

Callable object

In C++, a callable object is a broad concept that covers any entity that can be called via the function call operator (). These objects include not only ordinary functions and member functions of a class, but also objects, which can be called like functions. Here are some types of callable objects in C++ and how to use them:

Common functions

Ordinary function types can declare functions, define function pointers, and function references, but they cannot directly define function entities using function types.

#include <iostream>
using namespace std;

using Fun = void(int, const string & amp;); // Alias for ordinary functions
Fun greet; // Declare a normal function

// Ordinary function declaration
//void greet(int id, const string & amp; message);

int main() {<!-- -->
    greet(1, "Hello, World!"); // Directly call ordinary functions

    // Call function through function pointer
    void(*funcPtr)(int, const string & amp;) = greet;
    funcPtr(2, "Hello again!");

    // Call function via function reference
    void( & amp;funcRef)(int, const string & amp;) = greet;
    funcRef(3, "Hello once more!");

    return 0;
}

// Ordinary function definition
void greet(int id, const string & amp; message) {<!-- -->
    cout << "Message " << id << ": " << message << endl;
}
Static member function of class

Static member functions of a class are functions that belong to the class, not a specific instance of the class. **They can be called without creating an object of the class. **Static member functions are generally used for functionality related to the class rather than to a specific object. Take a look at the following example:

#include <iostream>
using namespace std;

class Messenger {<!-- -->
public:
    // Static member function declaration
    static void greet(int id, const string & amp; message);
};

int main() {<!-- -->
    Messenger::greet(1, "Hello, World!"); // Directly call the static member function
    
    // Call static member function through function pointer
    void(*funcPtr)(int, const string & amp;) = Messenger::greet;
    funcPtr(2, "Hello again!");
    
    // Call static member function through function reference
    void( & amp;funcRef)(int, const string & amp;) = Messenger::greet;
    funcRef(3, "Hello once more!");
    
    return 0;
}

// Static member function definition
void Messenger::greet(int id, const string & amp; message) {<!-- -->
    cout << "Message " << id << ": " << message << endl;
}
Functions

The essence of a functor is a class, which is just an overloaded function call operator operator().

Here is an example using functors:

#include <iostream>
using namespace std;

//Define a functor class
class Greeting {<!-- -->
public:
    void operator()(int id, const string & amp; msg) {<!-- -->
        cout << "Greetings " << id << ", " << msg << endl;
    }
};

int main() {<!-- -->
    Greeting greet; // Create an instance of the functor class
    greet(101, "Welcome to the team!"); // Called via functor instance

    Greeting()(102, "Happy coding!"); // Called via anonymous functor instance

    Greeting & amp; refGreet = greet; // functor reference
    refGreet(103, "Have a great day!"); // Called by functor reference

    return 0;
}
lambda function

The essence of the lambda function is a functor, but it provides a more concise way to define local function objects.

#include <iostream>
using namespace std;

int main() {<!-- -->
    //Define a lambda expression and assign it to the auto variable
    auto sayHello = [](int id, const string & amp; msg) {<!-- -->
        cout << "Hello " << id << ", " << msg << endl;
    };

    // Call directly through lambda object
    sayHello(201, "welcome to lambda functions!");

    // Called by reference to lambda object
    auto & refSayHello = sayHello;
    refSayHello(202, "lambdas are powerful!");

    return 0;
}
Non-static member functions of classes

The non-static member functions of a class are closely related to the object of the class, so we must call these functions through the entity of the class.

The non-static member functions of a class only have pointer types, not reference types, and cannot be referenced.

The non-static member functions of the class have addresses. When using function pointers, you need to take the address symbols & amp;.

#include <iostream>
using namespace std;

class MessagePrinter {<!-- -->
public:
    void display(int id, const string & amp; msg) {<!-- -->
        cout << "Message " << id << ": " << msg << endl;
    }
};

int main() {<!-- -->
    MessagePrinter printer;
    printer.display(21, "Hello, World!");

    //Declaration and use of member function pointers
    void (MessagePrinter::*funcPtr)(int, const string & amp;) = & amp;MessagePrinter::display;
    (printer.*funcPtr)(22, "This is a member function pointer");

    //Member function pointer in C++11
    auto funcPtr2 = & amp;MessagePrinter::display;
    (printer.*funcPtr2)(23, "This is a member function pointer in c++11");
    
    //Member function pointer in C++11, use using keyword
    using pFun = void (MessagePrinter::*)(int, const string & amp;);
    pFun funcPtr3 = & amp;MessagePrinter::display;
    (printer.*funcPtr3)(24, "This is a member function pointer in c++11 with using");
    
    return 0;
}
Class object that can be converted to a function pointer

A class can overload the type conversion operator operator data type (). If the data type is a function pointer or function reference type, then the class instance will also become a callable object.

Its essence is a class, and the calling code is like a function.

In actual development, it is of little significance.

Example below: no need to look

#include <iostream>
using namespace std;

//Define a normal function
void displayMessage(int id, const string & amp; msg) {<!-- -->
    cout << "Message " << id << ": " << msg << endl;
}

// Define a class that can be converted to a function pointer
class ConvertibleToFunctionPointer {<!-- -->
public:
    using FunctionType = void (*)(int, const string & amp;);

    // Overloaded type conversion operator
    operator FunctionType() const {<!-- -->
        return displayMessage;
    }
};

int main() {<!-- -->
    ConvertibleToFunctionPointer convert;
    convert(23, "Callable like a function");

    return 0;
}

Wrapper function

std::function is a template class that can be used to encapsulate any type of callable object. The definition of this template class is roughly as follows:

template <typename T>
class function;

Where T is a function type, for example, void(int) represents a function that accepts int parameters and returns void.

You need to include the header file when using it

Note:

  • std::function overloads the bool type conversion operator, which means you can use std directly in the if statement ::function object to check whether it contains a callable object.
  • If a std::function object does not wrap any callable object, attempting to call it will throw a std::bad_function_call exception

Instructions:

For example, there is a common global function:

void greet (int id , const string & amp; asg){<!-- -->
    cout<< "Message " << id << ": " << asg << endl;
}

You can use function to wrap this function:

 function <void(int, const string & amp;)> func = greet;
    func(1, "Hello");

As mentioned above, if function is not wrapped with a callable object, an exception will be thrown. Please see the example:

 function <void(int, const string & amp;)> func;
    try{<!-- -->
        func(1, "Hello");
    }catch (const bad_function_call & amp; e){<!-- -->
        cout << e.what() << endl;
    }

See the complete example below:

#include <iostream>
#include <functional>
#include <string>

using namespace std;

// Ordinary function
void greet(int id, const string & amp; message) {<!-- -->
    cout << "user" << id << "," << message << endl;
}

//Static member function of the class
struct User {<!-- -->
    static void staticGreet(int id, const string & amp; message) {<!-- -->
        cout << "user" << id << "," << message << endl;
    }
};

// Function object (functor)
struct GreetFunc {<!-- -->
    void operator()(int id, const string & amp; message) const {<!-- -->
        cout << "user" << id << "," << message << endl;
    }
};

//Non-static member functions of the class
struct MemberFunc {<!-- -->
    void memberGreet(int id, const string & amp; message) {<!-- -->
        cout << "user" << id << "," << message << endl;
    }
};

// Classes that can be converted to function pointers
struct ConvertibleFunc {<!-- -->
    using FuncType = void (*)(int, const string & amp;);
    operator FuncType() const {<!-- -->
        return greet; // Assume here that greet is the global function defined before
    }
};

int main() {<!-- -->
    // wrap ordinary function
    function<void(int, const string & amp;)> func1 = greet;
    func1(1, "Welcome to the world of C++.");

    // wrap static member function
    function<void(int, const string & amp;)> func2 = User::staticGreet;
    func2(2, "Welcome to the world of C++.");

    // wrapper function object (functor)
    GreetFunc greetFunc;
    function<void(int, const string & amp;)> func3 = greetFunc;
    func3(3, "Welcome to the world of C++.");

    // wrap Lambda expression
    function<void(int, const string & amp;)> func4 = [](int id, const string & amp; message) {<!-- -->
        cout << "user" << id << "," << message << endl;
    };
    func4(4, "Welcome to the world of C++.");

    // wrap member function
    MemberFunc obj;
    auto memberFunc = bind( & amp;MemberFunc::memberGreet, & amp;obj, placeholders::_1, placeholders::_2);
    function<void(int, const string & amp;)> func5 = memberFunc;
    func5(5, "Welcome to the world of C++.");

    // Wrap a class object that can be converted to a function pointer
    ConvertibleFunc convFunc;
    function<void(int, const string & amp;)> func6 = convFunc;
    func6(6, "Welcome to the world of C++.");

    //Try to call an empty function object
    function<void(int, const string & amp;)> emptyFunc;
    try {<!-- -->
        emptyFunc(7, "Attempt to call an empty function object.");
    } catch (bad_function_call & amp; e) {<!-- -->
        cout << "Exception caught:" << e.what() << endl;
    }

    return 0;
}
The difference between wrappers and function pointers

After reading the above example, you may have a question. The wrapper function and the function pointer look exactly the same. They both give the function object an alias, and then you can call it using the alias. Here I explain in detail the difference between wrappers and function pointers.

Versatility:

  • function is a template class that can wrap almost any type of callable entity.
  • Function pointers can only point to ordinary non-member functions.

Unified Grammar:

  • function provides a unified way to deal with all callable objects, regardless of their type.
  • The syntax of a function pointer must exactly match the return type and parameter types of the function it points to.

Status and behavior:

  • function can store state; for example, it can wrap a lambda expression or function object that binds specific data.
  • Function pointers are stateless, they only represent the address of a function.

Security:

  • function overloads the bool operator, which can be used to check whether a callable object is wrapped.
  • If a function pointer is nullptr and called, it may cause undefined behavior.

Therefore, when writing programs, it is more inclined to use the wrapper function.

Adapter bind

std::bindIt allows you to bind a function, method or function object to its parameters or part of its parametersto create a new callable object.

You need to include the header file when using it

template <class Fx, class... Args>
function <> bind(Fx & amp; & amp; fx, Args & amp; & amp;... args)

Fx: the callable object that needs to be bound

args: binding parameter list, which can be lvalue, rvalue and parameter placeholder std::placeholders::_n, if the parameter is not a placeholder, it defaults It is passed by value, and std::ref(parameter) is passed by reference.

Another use of bind is to change the order of function parameters. Sometimes you may have a function that accepts two parameters, but you want to create a new function that accepts these two parameters in the reverse order. std::bind makes this easy, just pass the placeholders in the order you want.

For example:

void print(int a, int b) {<!-- -->
   cout << "a: " << a << ", b: " << b << endl;
}

The original sequential call is print(1, 2);

Use bind to change parameter order

auto reversedPrint = std::bind(print, placeholders::_2, placeholders::_1);

Here placeholders::_1 represents the first parameter passed when the binding object is called.

In addition, the number of parameters in the parameter list of the bound object depends on how many placeholders you need to provide (assuming that the values of some parameters are not fixed in advance)

When using std::bind, if arguments are not placeholders, they are passed by value by default. If you want to pass by reference, you can use std::ref or std::cref.

void increment(int & amp; value) {<!-- -->
     + + value;
}

int main() {<!-- -->
    int x = 0;

    // Use std::ref to pass a reference to x
    auto incrementX = bind(increment, ref(x));

    incrementX(); // Increase the value of x

    cout << "x: " << x << endl; // Output: x: 1

    return 0;
}

Finally, it is worth noting that std::bind returns an object of type std::function. This means you can store the result of the binding in std::function or use it directly to call the function.

See an example below:

#include <iostream>
#include <functional>
using namespace std;

// Ordinary function
void show(int bh, const string & amp; message) {<!-- -->
    cout << " " << bh << "No.," << message << endl;
}

int main()
{<!-- -->
    // Directly assign the ordinary function to the function object fn1
    function<void(int, const string & amp;)> fn1 = show;
    // Use bind to bind the parameters of the ordinary function show to the placeholder and create a callable object fn2
    function<void(int, const string & amp;)> fn2 = bind(show, placeholders::_1, placeholders::_2);

    fn1(1, "adapter bind");
    fn2(1, "adapter bind");

    // Reverse the order of the parameters of the function show and bind it to the placeholder to create a callable object fn3
    function<void(const string & amp;, int)> fn3 = bind(show, placeholders::_2, placeholders::_1);
    fn3("adapter bind", 1);

    // Fix the first parameter of the function show to 3, bind the second parameter to the placeholder, and create the callable object fn4
    function<void(const string & amp;)> fn4 = bind(show, 3, placeholders::_1);
    fn4("adapter bind");

    // The parameter provided in bind is one more than the show function, but the extra parameters will be ignored and a callable object fn5 is created.
    function<void(int, const string & amp;,int)> fn5 = bind(show, placeholders::_1, placeholders::_2);
    fn5(8, "adapter bind", 1);
}

See below for complete examples of the six callable objects:

#include <iostream>
#include <functional>
using namespace std;

// Ordinary function
void show(int bh, const string & amp; message) {<!-- -->
    cout << "number" << bh << "," << message << endl;
}

struct AA // There are static member functions in the class.
{<!-- -->
    static void show(int bh, const string & amp; message) {<!-- -->
        cout << "number" << bh << "," << message << endl;
    }
};

struct BB // Functor.
{<!-- -->
    void operator()(int bh, const string & amp; message) {<!-- -->
        cout << "number" << bh << "," << message << endl;
    }
};

struct CC // There are ordinary member functions in the class.
{<!-- -->
    void show(int bh, const string & amp; message) {<!-- -->
        cout << "number" << bh << "," << message << endl;
    }
};

struct DD // A class that can be converted to an ordinary function pointer.
{<!-- -->
    using Fun = void (*)(int, const string & amp;); // Alias for function pointer.
    operator Fun() {<!-- -->
        return show; // Return the address of the ordinary function show.
    }
};

int main()
{<!-- -->
    // Ordinary function.
    function<void(int, const string & amp;)> fn1 = bind(show, placeholders::_1, placeholders::_2);
    fn1(1, "adapter bind");

    // Static member function of the class.
    function<void(int, const string & amp;)> fn3 = bind(AA::show, placeholders::_1, placeholders::_2);
    fn3(2, "adapter bind");

    // functor.
    function<void(int, const string & amp;)> fn4 = bind(BB(), placeholders::_1, placeholders::_2);
    fn4(3, "adapter bind");

    //Create lambda object.
    auto lb = [](int bh, const string & amp; message) {<!-- -->
        cout << "number" << bh << "," << message << endl;
    };
    function<void(int, const string & amp;)> fn5 = bind(lb, placeholders::_1, placeholders::_2);
    fn5(4, "adapter bind");

    // Non-static member functions of the class.
    CC cc;
    function<void(int, const string & amp;)> fn11 = bind( & amp;CC::show, & amp;cc,placeholders::_1, placeholders::_2);
    fn11(5, "adapter bind");

    // A class object that can be converted to a function pointer.
    DD dd;
    function<void(int, const string & amp;)> fn12 = bind(dd, placeholders::_1, placeholders::_2);
    fn12(6, "adapter bind");
}

Variable functions and parameters

Using bind to bind a function and its parameters and execute the function is very similar to the constructor of the thread class, which allows you to start a thread in a similar way.

#include <iostream>
#include <functional>

using namespace std;

//Define a parameterless function
void greet() {<!-- -->
    cout << "Hello, World!" << endl;
}

//Define a function with parameters
void customGreet(const string & amp; name) {<!-- -->
    cout << "Hello, " << name << "!" << endl;
}

//Define a template function that accepts a function object and parameters, and then executes the function
template<typename Fn, typename... Args>
void execute(Fn & amp; & amp; fn, Args & amp; & amp;... args) {<!-- -->
    auto boundFunc = bind(forward<Fn>(fn), forward<Args>(args)...);
    boundFunc(); // Execute the bound function
}

int main() {<!-- -->
    //Call function without parameters
    execute(greet);

    //Call function with parameters
    execute(customGreet, "Alice");

    return 0;
}

Implementation of callback function

Callback functions are a commonly used technique in programming, especially useful when dealing with asynchronous operations. When something happens (for example, data is received), a callback function is called to handle the event. This mechanism is particularly common in scenarios such as message queues and network communications. The following will explain in detail how to implement callback functions in C++, especially in a simple message queue model.

First, we use the production and consumption model we learned before to implement a message queue for storing and delivering messages.

Then we create an interface for the callback function. Wrap with function.

First we define the message processing function and class:

#include <iostream>
#include <string>
#include <functional>

using namespace std;

void handleData(const string & amp; message) {<!-- -->
    cout << "Processing data:" << message << endl;
}

class DataHandler {<!-- -->
public:
    void handleData(const string & amp; message) {<!-- -->
        cout << "Processing data (class members):" << message << endl;
    }
};

Next, define the message queue class:

#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>

class MessageQueue {<!-- -->
    mutex m_mutex;
    condition_variable m_cond;
    queue<string> m_q;
    function<void(const string & amp;)> m_callback;

public:
    //Register callback function. This function will be called when a message arrives.
    void registerCallback(const function<void(const string & amp;)> & amp; callback) {<!-- -->
        m_callback = callback;
    }

     //Production data function. This function is called externally to add messages to the queue.
    void produceData(const string & amp; data) {<!-- -->
        unique_lock<mutex> lock(m_mutex);
        m_q.push(data);
        m_cond.notify_one(); // Notify a waiting consumer thread
    }

    //Consumption data function. The thread will run this function to consume the message.
    void consumeData() {<!-- -->
        while (true) {<!-- -->
            unique_lock<mutex> lock(m_mutex);
             Wait for the condition variable until the queue is not empty
            m_cond.wait(lock, [this] {<!-- --> return !m_q.empty(); });

            string message = m_q.front();
            m_q.pop();
            lock.unlock();

            if (m_callback) {<!-- --> // If a callback function is set
                m_callback(message); // Call the callback function to process the message
            }
        }
    }
};

Finally use this model in the main function:

int main() {<!-- -->
    MessageQueue queue; // Create an instance of MessageQueue

    // Register a normal function as a callback
    queue.registerCallback(handleData);

    //Register class member function as callback
    DataHandler handler;
    queue.registerCallback(bind( & amp;DataHandler::handleData, & amp;handler, placeholders::_1));

    thread consumerThread( & MessageQueue::consumeData, & amp;queue); // Create a consumer thread

    this_thread::sleep_for(chrono::seconds(1)); // Wait for a while
    queue.produceData("Hello, World!"); // Produce messages

    this_thread::sleep_for(chrono::seconds(1)); // Wait for some more time
    queue.produceData("Another message"); // Produce another message

    consumerThread.join(); // Wait for the consumer thread to end
    return 0;
}

In this example, we use std::function to store callback functions, and std::bind to bind class member functions as callbacks. This way we can flexibly handle the received messages.

How to replace virtual functions

C++ virtual functions have an additional cost when executing procedures because they involve dynamic binding, where the decision on which function to call is made at runtime. This is the basis of polymorphism, implemented in C++ through virtual function tables (vtables). Every class with virtual functions has a vtable, which is an array of function pointers that stores pointers to the class’s virtual functions. When calling a virtual function through a base class pointer or reference, the following steps occur:

  1. Look up the object’s virtual function table (vtable): This requires a memory access.
  2. Find the real virtual function address through the virtual function table: this is another memory access
  3. Finally, jump to the actual address of the function and execute it.

In contrast, non-virtual functions (ordinary functions) are statically bound and can be called directly once, with less overhead than virtual function calls.

In order to avoid the overhead of virtual functions, through function and bind, you can register the member functions of the subclass into the base class, and then call these functions through the base class, thereby simulating Virtual function behavior, but without using dynamic binding.

#include <iostream>
#include <functional>
using namespace std;

struct Vehicle {<!-- --> // Vehicle base class
    function<void()> m_callback; // Member function used to bind subclasses.

    //Register subclass member functions. Subclass member functions have no parameters.
    template<typename Fn, typename ...Args>
    void registerCallback(Fn & amp; & amp; fn, Args & amp; & amp;...args) {<!-- -->
        m_callback = bind(forward<Fn>(fn), forward<Args>(args)...);
    }

    void move() {<!-- -->
        if (m_callback) {<!-- -->
            m_callback(); // Call the member function of the subclass.
        }
    }
};

struct Car : public Vehicle {<!-- --> // Car derived class
    void drive() {<!-- --> cout << "Car is driving.\
"; }
};

struct Bike : public Vehicle {<!-- --> // Bike derived class
    void ride() {<!-- --> cout << "Bike is riding.\
"; }
};

int main() {<!-- -->
    //Move according to the transportation method selected by the user.
    int choice = 0;
    cout << "Please select a means of transportation (1-car; 2-bicycle):";
    cin >> choice;

    Vehicle* vehiclePtr = nullptr;

    if (choice == 1) {<!-- --> // 1-car
        vehiclePtr = new Car;
        vehiclePtr->registerCallback( & amp;Car::drive, static_cast<Car*>(vehiclePtr)); // Register subclass member function.
    } else if (choice == 2) {<!-- --> // 2-Bicycle
        vehiclePtr = new Bike;
        vehiclePtr->registerCallback( & amp;Bike::ride, static_cast<Bike*>(vehiclePtr)); // Register subclass member function.
    }

    if (vehiclePtr != nullptr) {<!-- -->
        vehiclePtr->move(); // Call the member function of the subclass.
        delete vehiclePtr; // Release the derived class object.
    }
}