Skip to content

Wiki

Function pointer

About 26 min read

Existing English translations were reused. 2 current source blocks remain in Simplified Chinese, without triggering paid translation.

1.Function pointer

Reading tip: The first half of this chapter uses only functions, pointers, and classes that have already been learned, and must be mastered in order. The second half expands to nullptr , using , Lambda, and std::function and other modern C++ features. The extension part explains the basic usage of new syntax and APIs in advance, but you need to finish the later "Modern C++" chapter to fully understand it. On a first pass, you can learn the conclusions first and temporarily skip the extension part.

1.1.Basic Concepts of Function Pointers

You have already learned: an ordinary pointer can store the address of a variable, and through the pointer you can indirectly access the variable.

After compilation, functions are also stored in memory, so functions likewise have addresses.A function pointer is a pointer variable that stores the address of a functionThrough a function pointer, the function it points to can be called indirectly.

Function pointers are commonly used for:

  • Pass a function to another function, that is, a callback function
  • Select different processing functions during program execution
  • Put multiple functions with the same form into a table for unified management
  • Save and call a class's member function

Assume the following function:

int add(int a, int b);

A function pointer to it can be declared as:

int (*function_pointer)(int, int);

This declaration can be read outward from the variable name:

  1. function_pointer is the variable name.
  2. *function_pointer indicates that it is a pointer.
  3. (int, int) indicates that the function it points to takes two int parameters.
  4. The leading int indicates that the function returns a int.

The basic syntax of a function pointer is:

返回值类型 (*函数指针变量名)(参数类型列表);

The parentheses on both sides of a variable name cannot be omitted:

int (*operation)(int, int); // operation 是函数指针
int *operation(int, int);   // operation 是函数,返回值是 int*

Summary: A function pointer type includes not only the return type but also the complete parameter type list.

1.2.Example 1: Save the function address and call the function

In most expressions, a function name is automatically converted to the function address, so you can directly assign add to a function pointer. It can also be written as &addwhere & indicates taking the function address, and the two forms have the same effect.

Calling a function through a function pointer also has two forms:

function_pointer(参数);    // 推荐写法
(*function_pointer)(参数); // 先解引用再调用

Example:

#include <iostream>

int add(int a, int b)
{
    return a + b;
}

int main()
{
    int (*function_pointer)(int, int) = add;

    std::cout << function_pointer(3, 5) << "\n";
    std::cout << (*function_pointer)(10, 20) << "\n";

    function_pointer = &add;
    std::cout << function_pointer(7, 8) << "\n";

    return 0;
}

Operation result:

8
30
15

Summary: Typically, directly assign the function name and use function_pointer(...) to call it; the code is more concise.

1.3.Example 2: The function pointer type must match

A function pointer can only point to functions with compatible types. The return type and the type of each parameter must match.

Example:

#include <iostream>

int add(int a, int b)
{
    return a + b;
}

double divide(double a, double b)
{
    return a / b;
}

int main()
{
    int (*integer_operation)(int, int) = add;
    double (*decimal_operation)(double, double) = divide;

    std::cout << integer_operation(4, 6) << "\n";
    std::cout << decimal_operation(7.0, 2.0) << "\n";

    // integer_operation = divide;
    // 错误:divide 的类型是 double(double, double),类型不匹配

    return 0;
}

Operation result:

10
3.5

Note: Do not rely on implicit type conversions that may occur when calling arguments. The type of the function itself must be compatible with the function pointer type.

1.4.Example 3: Switching between different functions at runtime

As long as the function type is the same, a function pointer can point to different functions at different times. This can separate "choosing which operation to execute" from "executing the operation".

Example:

#include <iostream>

int add(int a, int b)
{
    return a + b;
}

int subtract(int a, int b)
{
    return a - b;
}

int main()
{
    int (*operation)(int, int) = add;
    std::cout << "add: " << operation(10, 4) << "\n";

    operation = subtract;
    std::cout << "subtract: " << operation(10, 4) << "\n";

    return 0;
}

Operation result:

add: 14
subtract: 6

1.5.Example 4: Passing a function pointer as an argument—callback function

A function pointer can also be used as a parameter of another function. A function that is passed in and called by other code is usually called a callback function.

A callback function is not a new type of function. "Callback" describes how a function is used: the caller hands the function address to another piece of code, and that other piece of code calls this function when needed.

The following calculate does not care whether addition or multiplication is actually performed; it only requires that the passed-in function can accept two int and return int.

Example:

#include <iostream>

int add(int a, int b)
{
    return a + b;
}

int multiply(int a, int b)
{
    return a * b;
}

int calculate(int a, int b, int (*operation)(int, int))
{
    return operation(a, b);
}

int main()
{
    std::cout << calculate(6, 4, add) << "\n";
    std::cout << calculate(6, 4, multiply) << "\n";

    return 0;
}

Operation result:

10
24

Summary: Callbacks can keep calculate unchanged and alter the computation behavior only by passing in different functions.

1.6.Example 5: When a function pointer is used as a parameter, the parameter name can also be omitted

A function declaration only needs to tell the compiler the parameter types; it is not necessary to write parameter names. Function pointer parameters also follow this rule.

The following two function declarations have the same meaning:

int calculate(int a, int b, int (*operation)(int, int));
int calculate(int, int, int (*)(int, int));

The first form is easier to read, and it is usually recommended to retain meaningful parameter names in tutorials, header files, and function definitions.

Example:

#include <iostream>

void print_number(int value)
{
    std::cout << value << "\n";
}

void run_twice(int value, void (*callback)(int))
{
    callback(value);
    callback(value);
}

int main()
{
    run_twice(42, print_number);
    return 0;
}

Operation result:

42
42

1.7.Example 6: Array of Function Pointers

If multiple functions have the same type, their addresses can be stored in the same array, and the function can then be selected by array index.

The basic syntax of an array of function pointers is:

返回值类型 (*数组名[元素个数])(参数类型列表);

Array subscripts must be within a valid range. The following array has 3 elements, so the valid subscripts are 0、1 and 2.

Example:

#include <iostream>

int add(int a, int b)
{
    return a + b;
}

int subtract(int a, int b)
{
    return a - b;
}

int multiply(int a, int b)
{
    return a * b;
}

int main()
{
    int (*operations[3])(int, int) = {add, subtract, multiply};

    std::cout << operations[0](8, 2) << "\n";
    std::cout << operations[1](8, 2) << "\n";
    std::cout << operations[2](8, 2) << "\n";

    return 0;
}

Operation result:

10
6
16

This style of selecting behavior based on an index is called atable-drivenapproach. It can reduce very long if...else or switchsequences, but the range must be checked before using the index.

1.8.Example 7: Function Overloading and Function Pointers

We have already learned about function overloading earlier: multiple functions can use the same name, as long as their parameter lists are different.

When an overloaded function name is assigned to a function pointer with an explicit type, the compiler selects the matching overloaded version based on the type of the function pointer.

Example:

#include <iostream>

int square(int value)
{
    return value * value;
}

double square(double value)
{
    return value * value;
}

int main()
{
    int (*integer_square)(int) = square;
    double (*decimal_square)(double) = square;

    std::cout << integer_square(5) << "\n";
    std::cout << decimal_square(1.5) << "\n";

    return 0;
}

Operation result:

25
2.25

If there is insufficient type information, writing an overloaded function name alone may be ambiguous. The modern C++ extension section will introduce how to use explicit type casting to further specify the overloaded version.

2.Member function pointer

2.1.The difference between ordinary function pointers and member function pointers

Non-static member functions must be called on an object because the function may access the object's data members and the this pointer. Therefore, non-static member functions cannot be stored in ordinary function pointers; they require member function pointers.

The type of a pointer to a member function must include the class to which it belongs:

返回值类型 (类名::*指针变量名)(参数类型列表);

When taking the address of a member function, you must write &类名::函数名. When calling it, you also need to provide an object:

(对象.*成员函数指针)(参数);
(对象指针->*成员函数指针)(参数);

2.2.Example 8: Using an ordinary function pointer with a static member function

A static member function belongs to the class but is not attached to a specific object, and does not have a this pointer. Therefore, a regular function pointer can be used to store the address of a static member function.

Example:

#include <iostream>

class Calculator
{
public:
    static int add(int a, int b)
    {
        return a + b;
    }
};

int main()
{
    int (*operation)(int, int) = &Calculator::add;
    std::cout << operation(6, 9) << "\n";

    return 0;
}

Operation result:

15

2.3.Example 9: Calling a non-static member function pointer through an object

The following scale is a non-static member function that accesses the current object's factor_; therefore, a Multiplier object must be specified when calling it.

Operator .* represents "use member pointer through object". The parentheses around the call expression cannot be omitted, because the function call operator () has higher precedence than .*.

Example:

#include <iostream>

class Multiplier
{
private:
    int factor_;

public:
    Multiplier(int factor)
        : factor_(factor)
    {
    }

    int scale(int value)
    {
        return value * factor_;
    }
};

int main()
{
    int (Multiplier::*operation)(int) = &Multiplier::scale;

    Multiplier twice(2);
    Multiplier triple(3);

    std::cout << (twice.*operation)(5) << "\n";
    std::cout << (triple.*operation)(5) << "\n";

    return 0;
}

Operation result:

10
15

When the same member function pointer is called with different objects, it accesses each object's own data.

2.4.Example 10: Calling a pointer to a member function through an object pointer

If what is stored in the hand is an object pointer, the ->* operator must be used.

(对象指针->*成员函数指针)(参数);

Example:

#include <iostream>

class Counter
{
private:
    int value_;

public:
    Counter()
        : value_(0)
    {
    }

    void add(int amount)
    {
        value_ += amount;
    }

    int value()
    {
        return value_;
    }
};

int main()
{
    void (Counter::*action)(int) = &Counter::add;

    Counter counter;
    Counter* object_pointer = &counter;

    (object_pointer->*action)(5);
    (object_pointer->*action)(7);

    std::cout << counter.value() << "\n";

    return 0;
}

Operation result:

12

Summary: Use for objects .*; use for object pointers ->*.

2.5.Example 11:const Pointer to member function

成员函数参数列表后的 const 表示该成员函数不会修改对象的普通数据成员,可以在 const 对象上调用。

const is part of the member function type. Therefore, when pointing to const member functions, the function pointer declaration must also carry const.

Example:

#include <iostream>

class Sensor
{
private:
    int value_;

public:
    Sensor(int value)
        : value_(value)
    {
    }

    int read() const
    {
        return value_;
    }
};

int main()
{
    int (Sensor::*reader)() const = &Sensor::read;

    const Sensor sensor(42);
    std::cout << (sensor.*reader)() << "\n";

    return 0;
}

Operation result:

42

The following two member function pointer types are not the same:

int (Sensor::*reader1)();       // 指向非 const 成员函数
int (Sensor::*reader2)() const; // 指向 const 成员函数

3.Modern C++ Extensions (Optional Reading)

The following content is intended to explain the connection between function pointers and modern C++ features. Here, the basic syntax required for the examples will be explained, but a complete understanding still requires studying the later "Modern C++" chapter. Beginners can first read the conclusion and come back to review it later.

3.1.Extended example 1: Use nullptr to represent a null function pointer

Required standard version: C++11.

nullptr is the null pointer literal introduced in C++11, representing "pointing to no object or function". It is clearer than the older-style NULL and numeric 0 types.

Function pointers can be placed in if conditional checks: a non-null pointer converts to true, and a null pointer converts to false.Calling a null function pointer causes undefined behavior; therefore, it must be checked first when it may be null.

Example:

#include <iostream>

void greet()
{
    std::cout << "hello\n";
}

int main()
{
    void (*task)() = nullptr;

    if (task)
    {
        task();
    }
    else
    {
        std::cout << "task is empty\n";
    }

    task = greet;

    if (task != nullptr)
    {
        task();
    }

    return 0;
}

Operation result:

task is empty
hello

Note: A function pointer only stores the function address and is not responsible for memory management; it does not need and cannot use delete.nullptrThe full usage of is described in the later nullptr section.

3.2.Extended example 2: Use using to create an alias for a function pointer type

Required standard version: C++11.

Writing function pointer types directly is rather difficult. C++11 You can use using to give an alias to an existing type:

using 新名字 = 已有类型;

The following statement indicates that Operation is an alias of int (*)(int, int).

using Operation = int (*)(int, int);

using It does not create new types, nor does it incur runtime overhead; it merely makes the types easier to read.

Example:

#include <iostream>

using Operation = int (*)(int, int);

int add(int a, int b)
{
    return a + b;
}

int multiply(int a, int b)
{
    return a * b;
}

int calculate(int a, int b, Operation operation)
{
    return operation(a, b);
}

int main()
{
    Operation operation = add;
    std::cout << calculate(3, 8, operation) << "\n";

    operation = multiply;
    std::cout << calculate(3, 8, operation) << "\n";

    return 0;
}

Operation result:

11
24

You may also see the typedef syntax in old code, and both have the same meaning:

typedef int (*Operation1)(int, int);
using Operation2 = int (*)(int, int);

Modern C++ usually prefers the more readable using. For complete usage, see the later using section.

3.3.Extended example 3: returning a function pointer from a function

With type aliases, functions can also conveniently return a function pointer. The following select_operation returns the address of the corresponding function based on the character. If the character cannot be recognized, it returns nullptr.

Example:

#include <iostream>

using Operation = int (*)(int, int);

int add(int a, int b)
{
    return a + b;
}

int subtract(int a, int b)
{
    return a - b;
}

Operation select_operation(char symbol)
{
    if (symbol == '+')
    {
        return add;
    }

    if (symbol == '-')
    {
        return subtract;
    }

    return nullptr;
}

int main()
{
    Operation operation = select_operation('-');

    if (operation)
    {
        std::cout << operation(20, 7) << "\n";
    }
    else
    {
        std::cout << "unknown operation\n";
    }

    return 0;
}

Operation result:

13

Summary: Complex function pointer types should first use using to create an alias, then serve as parameter types or return value types.

3.4.Extended example 4: Captureless lambda conversion to a function pointer

Required standard version: C++11.

A lambda expression can define a small anonymous function at the point of use. The basic syntax is as follows:

[捕获列表](参数列表) -> 返回值类型
{
    函数体
};

The return type can usually be inferred by the compiler, so -> 返回值类型 is often omitted.

When the capture list is empty, that is, written as [], a lambda can be converted to an ordinary function pointer with a compatible signature.

Example:

#include <iostream>

using Operation = int (*)(int, int);

int calculate(int a, int b, Operation operation)
{
    return operation(a, b);
}

int main()
{
    Operation maximum = [](int a, int b) {
        return a > b ? a : b;
    };

    std::cout << calculate(12, 9, maximum) << "\n";
    std::cout << calculate(4, 7, [](int a, int b) {
        return a < b ? a : b;
    }) << "\n";

    return 0;
}

Operation result:

12
4

A capturing lambda needs to store the captured data in its own object, so it cannot be converted to an ordinary function pointer:

int offset = 10;

// Operation operation = [offset](int a, int b) {
//     return a + b + offset;
// }; // 错误:有捕获的 Lambda 不能转换为普通函数指针

For a complete explanation of capture lists and lambda types, see the Lambda Expressions section later.

3.5.Extended example 5: Use static_cast to select overloaded functions

static_cast<目标类型>(表达式) is the explicit type conversion syntax for C++. When taking the address of an overloaded function, it can be used to explicitly tell the compiler which overloaded version is needed.

auto lets the compiler deduce the variable type from the expression on the right. The following decimal_square the actual type is still double (*)(double).

Example:

#include <iostream>

int square(int value)
{
    return value * value;
}

double square(double value)
{
    return value * value;
}

int main()
{
    int (*integer_square)(int) = square;
    auto decimal_square = static_cast<double (*)(double)>(square);

    std::cout << integer_square(5) << "\n";
    std::cout << decimal_square(1.5) << "\n";

    return 0;
}

Operation result:

25
2.25

Summary: Prefer using an explicit function pointer type to select an overload; when the context is still ambiguous, then use static_cast. For the complete conversion rules, see the C++ type conversion section later.

3.6.Extended example 6: Use std::function to store different callable objects

Required standard version: C++11.

std::function is a general-purpose callable object wrapper provided by the standard library <functional> header file. Its basic syntax is:

std::function<返回值类型(参数类型列表)>

For example,std::function<int(int, int)> can store a callable object that accepts two int and returns int", including ordinary functions, function pointers, matching lambdas, and function objects.

Example:

#include <functional>
#include <iostream>

int add(int a, int b)
{
    return a + b;
}

int main()
{
    std::function<int(int, int)> operation = add;
    std::cout << operation(3, 4) << "\n";

    int offset = 100;
    operation = [offset](int a, int b) {
        return a + b + offset;
    };

    std::cout << operation(3, 4) << "\n";

    return 0;
}

Operation result:

7
107

An ordinary function pointer cannot store a lambda with captures,std::function It can. However, std::function uses type erasure to implement a unified wrapper, which incurs additional indirect call overhead and may also involve dynamic memory allocation in some cases. Therefore, it should not be used unconditionally to replace function pointers in all scenarios.

See the following std::function section for complete usage and more examples.

3.7.函数指针、Lambda 与 std::function 的选择

Requirement Recommended Approach Description
Store a regular function or a captureless lambda Function pointer The type is simple and has no type erasure overhead
Store a lambda with captures The lambda's own type or std::function A regular function pointer cannot store captured data
Accept any type and only invoke it immediately Template parameter Usually preserves the concrete type and is optimized by the compiler
Store multiple callbacks uniformly in a member variable std::function Can uniformly wrap multiple callable objects
Invoke a non-static member function of a specified object Member function pointer The object must also be provided when invoking

The “template parameters” and “the Lambda's own type” here need to be understood in conjunction with the later chapters on templates and Lambdas. For initial learning, you only need to remember: function pointers are suitable for storing ordinary functions and captureless Lambdas; when the requirements become more complex, then consider other tools.

4.Common Errors

4.1.Error 1: Forgetting the parentheses on both sides of the variable name

int (*operation)(int, int); // 正确:函数指针
int *operation(int, int);   // 这是返回 int* 的函数声明

4.2.Error 2: Function type mismatch

void print(int value);

// int (*operation)(int, int) = print; // 错误:返回值和参数列表不匹配

4.3.Error 3: Calling a null function pointer

void (*task)() = nullptr;

// task(); // 错误:产生未定义行为

if (task)
{
    task(); // 正确:先检查再调用
}

4.4.Mistake 4: Using a plain function pointer to store a non-static member function

class Robot
{
public:
    void run();
};

// void (*task)() = &Robot::run;     // 错误
void (Robot::*task)() = &Robot::run; // 正确

4.5.Error 5: No object provided when calling a member function pointer

Robot robot;
void (Robot::*task)() = &Robot::run;

// task();          // 错误:没有对象
(robot.*task)();    // 正确

4.6.Error 6: Converting a capturing lambda to a function pointer

using Operation = int (*)(int);
int offset = 10;

// Operation operation = [offset](int value) {
//     return value + offset;
// }; // 错误

5.Summary

  • A function pointer stores the address of a function, and its type includes the function's return value and parameter list.
  • The function name can usually be directly assigned to a function pointer with a compatible signature.
  • Function pointers can be used for callbacks, runtime function selection, and function tables.
  • Static member functions can use ordinary function pointers.
  • Non-static member functions require member function pointers, and the object must be provided when calling them.
  • Objects use .*; object pointers use ->*.
  • const is part of the member function pointer type.
  • Function pointers that may be null must be checked before being called.
  • Modern C++ can use using to simplify types and create function pointers from captureless lambdas.
  • When multiple callable objects need to be stored uniformly, std::function can be used.