Team LiB
Previous Section Next Section

19.4. Pointer to Class Member

 

A pointer to member is a pointer that can point to a nonstatic member of a class. Normally a pointer points to an object, but a pointer to member identifies a member of a class, not an object of that class. static class members are not part of any object, so no special syntax is needed to point to a static member. Pointers to static members are ordinary pointers.

 

The type of a pointer to member embodies both the type of a class and the type of a member of that class. We initialize such pointers to point to a specific member of a class without identifying an object to which that member belongs. When we use a pointer to member, we supply the object whose member we wish to use.

 

To explain pointers to members, we’ll use a version of the Screen class from § 7.3.1 (p. 271):

 

 

class Screen {
public:
    typedef std::string::size_type pos;
    char get_cursor() const { return contents[cursor]; }
    char get() const;
    char get(pos ht, pos wd) const;
private:
    std::string contents;
    pos cursor;
    pos height, width;
};

 

19.4.1. Pointers to Data Members

 

As with any pointer, we declare a pointer to member using a * to indicate that the name we’re declaring is a pointer. Unlike ordinary pointers, a pointer to member also incorporates the class that contains the member. Hence, we must precede the * with classname:: to indicate that the pointer we are defining can point to a member of classname. For example:

 

 

// pdata can point to a string member of a const (or non const) Screen object
const string Screen::*pdata;

 

declares that pdata is a “pointer to a member of class Screen that has type const string.” The data members in a const object are themselves const. By making our pointer a pointer to const string member, we say that we can use pdata to point to a member of any Screen object, const or not. In exchange we can use pdata to read, but not write to, the member to which it points.

 

When we initialize (or assign to) a pointer to member, we say to which member it points. For example, we can make pdata point to the contents member of an unspecified Screen object as follows:

 

pdata = &Screen::contents;

 

Here, we apply the address-of operator not to an object in memory but to a member of the class Screen.

 

Of course, under the new standard, the easiest way to declare a pointer to member is to use auto or decltype:

 

auto pdata = &Screen::contents;

 
Using a Pointer to Data Member
 

It is essential to understand that when we initialize or assign a pointer to member, that pointer does not yet point to any data. It identifies a specific member but not the object that contains that member. We supply the object when we dereference the pointer to member.

 

Analogous to the member access operators, . and ->, there are two pointer-to-member access operators, .* and ->*, that let us supply an object and dereference the pointer to fetch a member of that object:

 

 

Screen myScreen, *pScreen = &myScreen;
// .* dereferences pdata to fetch the contents member from the object myScreen
auto s = myScreen.*pdata;
// ->* dereferences pdata to fetch contents from the object to which pScreen points
s = pScreen->*pdata;

 

Conceptually, these operators perform two actions: They dereference the pointer to member to get the member that we want; then, like the member access operators, they fetch that member from an object (.*) or through a pointer (->*).

 
A Function Returning a Pointer to Data Member
 

Normal access controls apply to pointers to members. For example, the contents member of Screen is private. As a result, the use of pdata above must have been inside a member or friend of class Screen or it would be an error.

 

Because data members are typically private, we normally can’t get a pointer to data member directly. Instead, if a class like Screen wanted to allow access to its contents member, it would define a function to return a pointer to that member:

 

 

class Screen {
public:
    // data is a static member that returns a pointer to member
    static const std::string Screen::*data()
        { return &Screen::contents; }
    // other members as before
};

 

Here we’ve added a static member to class Screen that returns a pointer to the contents member of a Screen. The return type of this function is the same type as our original pdata pointer. Reading the return type from right to left, we see that data returns a pointer to a member of class Screen that is a string that is const. The body of the function applies the address-of operator to the contents member, so the function returns a pointer to the contents member of Screen.

 

When we call data, we get a pointer to member:

 

 

// data() returns a pointer to the contents member of class Screen
const string Screen::*pdata = Screen::data();

 

As before, pdata points to a member of class Screen but not to actual data. To use pdata, we must bind it to an object of type Screen

 

 

// fetch the contents of the object named myScreen
auto s = myScreen.*pdata;

 

Exercises Section 19.4.1

 

Exercise 19.11: What is the difference between an ordinary data pointer and a pointer to a data member?

Exercise 19.12: Define a pointer to member that can point to the cursor member of class Screen. Fetch the value of Screen::cursor through that pointer.

Exercise 19.13: Define the type that can represent a pointer to the bookNo member of the Sales_data class.


 

19.4.2. Pointers to Member Functions

 

We can also define a pointer that can point to a member function of a class. As with pointers to data members, the easiest way to form a pointer to member function is to use auto to deduce the type for us:

 

 

// pmf is a pointer that can point to a Screen member function that is const
// that returns a char and takes no arguments
auto pmf = &Screen::get_cursor;

 

Like a pointer to data member, a pointer to a function member is declared using classname::*. Like any other function pointer (§ 6.7, p. 247), a pointer to member function specifies the return type and parameter list of the type of function to which this pointer can point. If the member function is a const member (§ 7.1.2, p. 258) or a reference member (§ 13.6.3, p. 546), we must include the const or reference qualifier as well.

 

As with normal function pointers, if the member is overloaded, we must distinguish which function we want by declaring the type explicitly (§ 6.7, p. 248). For example, we can declare a pointer to the two-parameter version of get as

 

 

char (Screen::*pmf2)(Screen::pos, Screen::pos) const;
pmf2 = &Screen::get;

 

The parentheses around Screen::* in this declaration are essential due to precedence. Without the parentheses, the compiler treats the following as an (invalid) function declaration:

 

 

// error: nonmember function p cannot have a const qualifier
char Screen::*p(Screen::pos, Screen::pos) const;

 

This declaration tries to define an ordinary function named p that returns a pointer to a member of class Screen that has type char. Because it declares an ordinary function, the declaration can’t be followed by a const qualifier.

 

Unlike ordinary function pointers, there is no automatic conversion between a member function and a pointer to that member:

 

 

// pmf points to a Screen member that takes no arguments and returns char
pmf = &Screen::get; // must explicitly use the address-of operator
pmf = Screen::get;  // error: no conversion to pointer for member functions

 
Using a Pointer to Member Function
 

As when we use a pointer to a data member, we use the .* or ->* operators to call a member function through a pointer to member:

 

 

Screen myScreen,*pScreen = &myScreen;
// call the function to which pmf points on the object to which pScreen points
char c1 = (pScreen->*pmf)();
// passes the arguments 0, 0 to the two-parameter version of get on the object myScreen
char c2 = (myScreen.*pmf2)(0, 0);

 

The calls (myScreen->*pmf)() and (pScreen.*pmf2)(0,0) require the parentheses because the precedence of the call operator is higher than the precedence of the pointer to member operators.

 

Without the parentheses,

 

myScreen.*pmf()

 

would be interpreted to mean

 

myScreen.*(pmf())

 

This code says to call the function named pmf and use its return value as the operand of the pointer-to-member operator (.*). However, pmf is not a function, so this code is in error.

 

Image Note

Because of the relative precedence of the call operator, declarations of pointers to member functions and calls through such pointers must use parentheses: (C::*p)(parms) and (obj.*p)(args).

 

 
Using Type Aliases for Member Pointers
 

Type aliases or typedefs (§ 2.5.1, p. 67) make pointers to members considerably easier to read. For example, the following type alias defines Action as an alternative name for the type of the two-parameter version of get:

 

 

// Action is a type that can point to a member function of Screen
// that returns a char and takes two pos arguments
using Action =
char (Screen::*)(Screen::pos, Screen::pos) const;

 

Action is another name for the type “pointer to a const member function of class Screen taking two parameters of type pos and returning char.” Using this alias, we can simplify the definition of a pointer to get as follows:

 

 

Action get = &Screen::get; // get points to the get member of Screen

 

As with any other function pointer, we can use a pointer-to-member function type as the return type or as a parameter type in a function. Like any other parameter, a pointer-to-member parameter can have a default argument:

 

 

// action takes a reference to a Screen and a pointer to a Screen member function
Screen& action(Screen&, Action = &Screen::get);

 

action is a function taking two parameters, which are a reference to a Screen object and a pointer to a member function of class Screen that takes two pos parameters and returns a char. We can call action by passing it either a pointer or the address of an appropriate member function in Screen:

 

 

Screen myScreen;
// equivalent calls:
action(myScreen);      // uses the default argument
action(myScreen, get); // uses the variable get that we previously defined
action(myScreen, &Screen::get); // passes the address explicitly

 

Image Note

Type aliases make code that uses pointers to members much easier to read and write.

 

 
Pointer-to-Member Function Tables
 

One common use for function pointers and for pointers to member functions is to store them in a function table (§ 14.8.3, p. 577). For a class that has several members of the same type, such a table can be used to select one from the set of these members. Let’s assume that our Screen class is extended to contain several member functions, each of which moves the cursor in a particular direction:

 

 

class Screen {
public:
    // other interface and implementation members as before
    Screen& home();     // cursor movement functions
    Screen& forward();
    Screen& back();
    Screen& up();
    Screen& down();
};

 

Each of these new functions takes no parameters and returns a reference to the Screen on which it was invoked.

 

We might want to define a move function that can call any one of these functions and perform the indicated action. To support this new function, we’ll add a static member to Screen that will be an array of pointers to the cursor movement functions:

 

 

class Screen {
public:
    // other interface and implementation members as before
    // Action is a pointer that can be assigned any of the cursor movement members
    using Action = Screen& (Screen::*)();
    // specify which direction to move; enum see § 19.3 (p. 832)
    enum Directions { HOME, FORWARD, BACK, UP, DOWN };
    Screen& move(Directions);
private:
    static Action Menu[];      // function table
};

 

The array named Menu will hold pointers to each of the cursor movement functions. Those functions will be stored at the offsets corresponding to the enumerators in Directions. The move function takes an enumerator and calls the appropriate function:

 

 

Screen& Screen::move(Directions cm)
{
    // run the element indexed by cm on this object
    return (this->*Menu[cm])(); // Menu[cm] points to a member function
}

 

The call inside move is evaluated as follows: The Menu element indexed by cm is fetched. That element is a pointer to a member function of the Screen class. We call the member function to which that element points on behalf of the object to which this points.

 

When we call move, we pass it an enumerator that indicates which direction to move the cursor:

 

 

Screen myScreen;
myScreen.move(Screen::HOME);  // invokes myScreen.home
myScreen.move(Screen::DOWN);  // invokes myScreen.down

 

What’s left is to define and initialize the table itself:

 

 

Screen::Action Screen::Menu[] = { &Screen::home,
                                  &Screen::forward,
                                  &Screen::back,
                                  &Screen::up,
                                  &Screen::down,
                                };

 

Exercises Section 19.4.2

 

Exercise 19.14: Is the following code legal? If so, what does it do? If not, why?

 

auto pmf = &Screen::get_cursor;
pmf = &Screen::get;

 

Exercise 19.15: What is the difference between an ordinary function pointer and a pointer to a member function?

Exercise 19.16: Write a type alias that is a synonym for a pointer that can point to the avg_price member of Sales_data.

Exercise 19.17: Define a type alias for each distinct Screen member function type.


 

19.4.3. Using Member Functions as Callable Objects

 

As we’ve seen, to make a call through a pointer to member function, we must use the .* or ->* operators to bind the pointer to a specific object. As a result, unlike ordinary function pointers, a pointer to member is not a callable object; these pointers do not support the function-call operator (§ 10.3.2, p. 388).

 

Because a pointer to member is not a callable object, we cannot directly pass a pointer to a member function to an algorithm. As an example, if we wanted to find the first empty string in a vector of strings, the obvious call won’t work:

 

 

auto fp = &string::empty;   // fp points to the string empty function
// error: must use .* or ->* to call a pointer to member
find_if(svec.begin(), svec.end(), fp);

 

The find_if algorithm expects a callable object, but we’ve supplied fp, which is a pointer to a member function. This call won’t compile, because the code inside find_if executes a statement something like

 

 

// check whether the given predicate applied to the current element yields true
if (fp(*it))  // error: must use ->* to call through a pointer to member

 

which attempts to call the object it was passed.

 
Using function to Generate a Callable
 

One way to obtain a callable from a pointer to member function is by using the library function template (§ 14.8.3, p. 577):

 

 

function<bool (const string&)> fcn = &string::empty;
find_if(svec.begin(), svec.end(), fcn);

 

Here we tell function that empty is a function that can be called with a string and returns a bool. Ordinarily, the object on which a member function executes is passed to the implicit this parameter. When we want to use function to generate a callable for a member function, we have to “translate” the code to make that implicit parameter explicit.

 

When a function object holds a pointer to a member function, the function class knows that it must use the appropriate pointer-to-member operator to make the call. That is, we can imagine that find_if will have code something like

 

 

// assuming it is the iterator inside find_if, so *it is an object in the given range
if (fcn(*it))  // assuming fcn is the name of the callable inside find_if

 

which function will execute using the proper pointer-to-member operator. In essence, the function class will transform this call into something like

 

 

// assuming it is the iterator inside find_if, so *it is an object in the given range
if (((*it).*p)()) // assuming p is the pointer to member function inside fcn

 

When we define a function object, we must specify the function type that is the signature of the callable objects that object can represent. When the callable is a member function, the signature’s first parameter must represent the (normally implicit) object on which the member will be run. The signature we give to function must specify whether the object will be passed as a pointer or a reference.

 

When we defined fcn, we knew that we wanted to call find_if on a sequence of string objects. Hence, we asked function to generate a callable that took string objects. Had our vector held pointers to string, we would have told function to expect a pointer:

 

 

vector<string*> pvec;
function<bool (const string*)> fp = &string::empty;
// fp takes a pointer to string and uses the ->* to call empty
find_if(pvec.begin(), pvec.end(), fp);

 
Using mem_fn to Generate a Callable
 
Image

To use function, we must supply the call signature of the member we want to call. We can, instead, let the compiler deduce the member’s type by using another library facility, mem_fn, which, like function, is defined in the functional header. Like function, mem_fn generates a callable object from a pointer to member. Unlike function, mem_fn will deduce the type of the callable from the type of the pointer to member:

 

 

find_if(svec.begin(), svec.end(), mem_fn(&string::empty));

 

Here we used mem_fn(&string::empty) to generate a callable object that takes a string argument and returns a bool.

 

The callable generated by mem_fn can be called on either an object or a pointer:

 

 

auto f = mem_fn(&string::empty); // f takes a string or a string*
f(*svec.begin()); // ok: passes a string object; f uses .* to call empty
f(&svec[0]);      // ok: passes a pointer to string; f uses .-> to call empty

 

Effectively, we can think of mem_fn as if it generates a callable with an overloaded function call operator—one that takes a string* and the other a string&.

 
Using bind to Generate a Callable
 

For completeness, we can also use bind10.3.4, p. 397) to generate a callable from a member function:

 

 

// bind each string in the range to the implicit first argument to empty
auto it = find_if(svec.begin(), svec.end(),
                  bind(&string::empty, _1));

 

As with function, when we use bind, we must make explicit the member function’s normally implicit parameter that represents the object on which the member function will operate. Like mem_fn, the first argument to the callable generated by bind can be either a pointer or a reference to a string:

 

 

auto f =  bind(&string::empty, _1);
f(*svec.begin()); // ok: argument is a string f will use .* to call empty
f(&svec[0]); // ok: argument is a pointer to string f will use .-> to call empty

 
Team LiB
Previous Section Next Section