Movatterモバイル変換


[0]ホーム

URL:




Chapter 16: Classes Having Pointers To Members

Classes havingpointer data members have been discussed in detail inchapter9. Classes defining pointer data-members deserve somespecial attention, as they usually require the definitions of copyconstructors, overloaded assignment operators and destructors

Situations exist where we do not need a pointer to an object but rather apointer to members of a class. Pointers to members can profitably be usedto configure the behavior of objects of classes. Depending on which member apointer to a member points to objects will show certain behavior.

Although pointers to members have their use, polymorphism can frequently beused to realize comparable behavior. Consider a class having a memberprocess performing one of a series of alternate behaviors. Instead ofselecting the behavior of choice at object construction time the class coulduse the interface of some (abstract) base class, passing an object of somederived class to its constructor and could thus configure its behavior. Thisallows for easy, extensible and flexible configuration, but access to theclass's data members would be less flexible and would possibly require the useof `friend' declarations. In such cases pointers to members may actually bepreferred as this allows for (somewhat less flexible) configuration as well asdirect access to a class's data members.

So the choice apparently is between on the one hand ease of configuration andon the other hand ease of access to a class's data members. In this chapterwe'll concentrate on pointers to members, investigating what these pointershave to offer.

16.1: Pointers to members: an example

Knowing how pointers to variables and objects are used does not intuitivelylead to the concept ofpointers to members. Even ifthe return types and parameter types of member functions are taken intoaccount, surprises can easily be encountered. For example, consider thefollowing class:
    class String    {        char const *(*d_sp)() const;        public:            char const *get() const;    };

For this class, it is not possible to letchar const *(*d_sp)() constpoint to theString::get member function asd_sp cannot be given theaddress of the member functionget.

One of the reasons why this doesn't work is that the variabled_sp hasglobal scope (it is a pointer toa function, not apointer to a function withinString), while the member functionget isdefined within theString class, and thus hasclass scope. The factthatd_sp is a data member of the classString is irrelevanthere. According tod_sp's definition, it points to a function livingsomewhereoutside of the class.

Consequently, to define a pointer to a member (either data or function, butusually a function) of a class, the scope of the pointer must indicateclass scope. Doing so, a pointer to the memberString::get is defined like this:

    char const *(String::*d_sp)() const;

So, by prefixing the*d_sp pointer data member byString::, it isdefined as a pointer in the context of the classString. According to itsdefinition it isa pointer to a function in the classString, notexpecting arguments, not modifying its object's data, and returning a pointerto constant characters.

16.2: Defining pointers to members

Pointers to members are defined by prefixing the normal pointer notationwith the appropriate class plus scope resolution operator. Therefore, in theprevious section, we usedchar const * (String::*d_sp)() const to indicatethatd_sp

The prototype of a matching function is therefore:

    char const *String::somefun() const;

which is anyconst parameterless function in the classString, returning achar const *.

When defining pointers to members the standard procedure for constructingpointers to functions can still be applied:

Here is another example, defining a pointerto a data member. Assume the classString contains astring d_textmember. How to construct a pointer to this member? Again we follow standardprocedure:

Alternatively, a very simplerule of thumb is

For example, the following pointer to a global function
    char const * (*sp)() const;

becomes a pointer to a member function after prefixing the class-scope:

    char const * (String::*sp)() const;

Nothing forces us to define pointers to members in their target(String) classes. Pointers to membersmay be defined in their targetclasses (so they become data members), or in another class, or as a localvariable or as a global variable. In all these cases the pointer to membervariable can be given the address of the kind of member it points to. Theimportant part is that a pointer to member can be initialized or assignedwithout requiring the existence of an object of the pointer's target class.

Initializing or assigning an address to such a pointer merely indicates towhich member the pointer points. This can be considered some kind ofrelative address; relative to the object for which the function iscalled. No object is required when pointers to members are initialized orassigned. While it is allowed to initialize or assign a pointer to member, itis (of course) not possible tocall those members without specifying anobject of the correct type.

In the following example initialization of and assignment topointers to members is illustrated (for illustration purposes all members ofthe classPointerDemo are definedpublic). In the example itself the&-operator is used to determine the addresses of the members. Theseoperators as well as the class-scopes are required. Even when used insidemember implementations:

    #include <cstddef>    class PointerDemo    {        public:            size_t d_value;            size_t get() const;    };    inline size_t PointerDemo::get() const    {        return d_value;    }    int main()    {                                           // initialization        size_t (PointerDemo::*getPtr)() const = &PointerDemo::get;        size_t PointerDemo::*valuePtr         = &PointerDemo::d_value;        getPtr   = &PointerDemo::get;           // assignment        valuePtr = &PointerDemo::d_value;    }
This involves nothing special. The difference with pointers atglobal scope is that we're now restricting ourselves to the scope of thePointerDemo class. Because of this restriction, allpointerdefinitions and all variables whose addresses are used must be given thePointerDemo class scope.

Pointers to members can also be used withvirtual member functions. No special syntax is required when pointing to virtualmembers. Pointer construction, initialization and assignment is doneidentically to the way it is done with non-virtual members.

16.3: Using pointers to members

Using pointers to members to call a member function requires the existence ofan object of the class of the members to which the pointer to member refersto. With pointers operating at global scope, the dereferencing operator*is used. With pointers to objects thefield selector operator operating onpointers (->) or the field selector operating operating on objects (.)can be used to select appropriate members.

To use a pointer to member in combination with an object the pointer to member field selector (.*) must be specified. To use apointer to a member via a pointer to an object the `pointer to member fieldselector through a pointer to an object' (->*) must be specified. Thesetwo operators combine the notions of a field selection (the. and->parts) to reach the appropriate field in an object and of dereferencing: adereference operation is used to reach the function or variable the pointer tomember points to.

Using the example from the previous section, let's see how we can usepointers to member functions and pointers to data members:

    #include <iostream>    class PointerDemo    {        public:            size_t d_value;            size_t get() const;    };    inline size_t PointerDemo::get() const    {        return d_value;    }    using namespace std;    int main()    {                                           // initialization        size_t (PointerDemo::*getPtr)() const = &PointerDemo::get;        size_t PointerDemo::*valuePtr   = &PointerDemo::d_value;        PointerDemo object;                     // (1) (see text)        PointerDemo *ptr = &object;        object.*valuePtr = 12345;               // (2)        cout << object.*valuePtr << '\n' <<                object.d_value << '\n';        ptr->*valuePtr = 54321;                 // (3)        cout << object.d_value << '\n' <<                (object.*getPtr)() << '\n' <<   // (4)                (ptr->*getPtr)() << '\n';    }
We note: Pointers to members can be used profitably in situations where a class hasa member that behaves differently depending on a configurationsetting. Consider once again the classPerson from section9.3.Person defines data members holding a person's name,address and phone number. Assume we want to construct aPersondatabase of employees. The employee database can be queried, but depending onthe kind of person querying the database either the name, the name and phonenumber or all stored information about the person is made available. Thisimplies that a member function likeaddress must return something like`<not available>' in cases where the person querying the database is notallowed to see the person's address, and the actual address in other cases.

The employee database is opened specifying an argument reflecting thestatus of the employee who wants to make some queries. The status couldreflect his or her position in the organization, likeBOARD,SUPERVISOR,SALESPERSON, orCLERK. The first two categories areallowed to see all information about the employees, aSALESPERSON isallowed to see the employee's phone numbers, while theCLERK is onlyallowed to verify whether a person is actually a member of the organization.

We now construct a memberstring personInfo(char const *name) in thedatabase class. A standard implementation of this class could be:

    string PersonData::personInfo(char const *name)    {        Person *p = lookup(name);   // see if `name' exists        if (!p)            return "not found";        switch (d_category)        {            case BOARD:            case SUPERVISOR:                return allInfo(p);            case SALESPERSON:                return noPhone(p);            case CLERK:                return nameOnly(p);        }    }

Although it doesn't take much time, theswitch must nonetheless beevaluated every timepersonInfo is called. Instead of using a switch, wecould define a memberd_infoPtr as a pointer to a member function of theclassPersonData returning astring and expecting a pointer to aPerson as its argument.

Instead of evaluating the switch this pointer can be used to point toallInfo,noPhone ornameOnly. Furthermore, the member functionthe pointer points to will be known by the time thePersonDataobject is constructed and so its value needs to be determined only once (atthe PersonData object's construction time).

Having initializedd_infoPtr thepersonInfo member function is nowimplemented simply as:

    string PersonData::personInfo(char const *name)    {        Person *p = lookup(name);       // see if `name' exists        return p ? (this->*d_infoPtr)(p) :  "not found";    }

The memberd_infoPtr is defined as follows (within the classPersonData, omitting other members):

    class PersonData    {        std::string (PersonData::*d_infoPtr)(Person *p);    };

Finally, the constructor initializesd_infoPtr. This could be realizedusing a simple switch:

    PersonData::PersonData(PersonData::EmployeeCategory cat)    {        switch (cat)        {            case BOARD:            case SUPERVISOR:                d_infoPtr = &PersonData::allInfo;            break;            case SALESPERSON:                d_infoPtr = &PersonData::noPhone;            break;            case CLERK:                d_infoPtr = &PersonData::nameOnly;            break;        }    }

Note how addresses of member functions are determined. The classPersonData scopemust be specified, even though we're already insidea member function of the classPersonData.

Since theEmployeeCategory values are known, theswitch in theabove constructor can also easily be avoided by defining a static array ofpointers to functions. The classPersonData defines the static array:

    class PersonData    {        std::string (PersonData::*d_infoPtr)(Person *p);        static std::string (PersonData::*s_infoPtr[])(Person *p);    };

ands_infoPtr[] can be initialized compile-time:

    string (PersonData::*PersonData::s_infoPtr[])(Person *p) =     {        &PersonData::allInfo,       // BOARD        &PersonData::allInfo,       // SUPERVISOR        &PersonData::noPhone,       // SALESPERSON        &PersonData::nameOnly       // CLERK    };

The constructor, instead of using aswitch, now directly calls therequired member from the appropriate array element:

    PersonData::PersonData(PersonData::EmployeeCategory cat)    :        d_infoPtr(s_infoPtr[cat])    {}

An example using pointers to data members is provided in section19.1.54, in the context of thestable_sort generic algorithm.

16.4: Pointers to static members

Static members of a class can be used without havingavailable an object of their class. Public static members can be called likefree functions, albeit that their class names must be specified when they arecalled.

Assume a classString has a public static member functioncount, returning the number of string objects created sofar. Then, without using anyString object the functionString::count may be called:

    void fun()    {        cout << String::count() << '\n';    }

Public static members can be called like free functions (but see alsosection8.2.1).Private static members can only becalled within the context of their class, by their class's member or friendfunctions.

Since static members have no associated objects their addresses can bestored in ordinary function pointer variables, operating at the globallevel. Pointers to members cannot be used to store addresses of staticmembers. Example:

    void fun()    {        size_t (*pf)() = String::count;                // initialize pf with the address of a static member function        cout << (*pf)() << '\n';                // displays the value returned by String::count()    }

16.5: Pointer sizes

An interestingcharacteristic of pointers to members is that their sizes differ from those of`normal' pointers. Consider the following little program:
    #include <string>    #include <iostream>    class X    {        public:            void fun();            std::string d_str;    };    inline void X::fun()    {        std::cout << "hello\n";    }    using namespace std;    int main()    {        cout <<           "size of pointer to data-member:     " << sizeof(&X::d_str) << "\n"           "size of pointer to member function: " << sizeof(&X::fun) << "\n"           "size of pointer to non-member data: " << sizeof(char *) << "\n"           "size of pointer to free function:   " << sizeof(&printf) << '\n';    }    /*        generated output (on 32-bit architectures):        size of pointer to data-member:     4        size of pointer to member function: 8        size of pointer to non-member data: 4        size of pointer to free function:   4    */
On a 32-bit architecture a pointer to a member function requires eightbytes, whereas other kind of pointers require four bytes (Using GNU's g++compiler).

Pointer sizes are hardly ever explicitly used, but their sizes may causeconfusion in statements like:

    printf("%p", &X::fun);

Of course,printf is likely not the right tool for displaying thevalue of theseC++ specific pointers. The values of these pointers can beinserted into streams when aunion, reinterpreting the 8-byte pointers asa series of size_tchar values, is used:

    #include <string>    #include <iostream>    #include <iomanip>    class X    {        public:            void fun();            std::string d_str;    };    inline void X::fun()    {        std::cout << "hello\n";    }    using namespace std;    int main()    {        union        {            void (X::*f)();            unsigned char *cp;        }            u = { &X::fun };        cout.fill('0');        cout << hex;        for (unsigned idx = sizeof(void (X::*)()); idx-- > 0; )            cout << setw(2) << static_cast<unsigned>(u.cp[idx]);        cout << '\n';    }

But why are their sizes different from the sizes of ordinary pointers? Toanswer this question let's first have a look at the familiarstd::fstream. It is derived fromstd::ifstream andstd::ofstream. Anfstream, therefore, contains both anifstream andanofstream. Anfstream will be organized as shown in figure24.

Figure 24: std::fstream object organization

Infstream (a) the first base class wasstd::istream, and the secondbaseclass wasstd::ofstream. But it could also very well be the other wayaround, as illustrated infstream (b): first thestd::ofstream, thenthestd::ifstream. And that's the crux of the biscuit.

If we have anfstream fstr{"myfile"} object and dofstr.seekg(0), thenwe callifstream's seekg function. But if we dofstr.seekp(0), then wecallofstream's seekp function. These functions have their own addresses,say &seekg and &seekp. But when we call a member function (likefstr.seekp(0)) then what we in fact are doing isseekp(&fstr, 0).

But the problem here is that&fstr does not represent the correct objectaddress:seekp operates on anofstream, and that object does not startat&fstr, so (infstream (a)), at&(fstr + sizeof(ifstream)).

So, the compiler, when calling a member function of a class using inheritance,must make a correction for the relative location of an object whose members weare calling.

However, when we're defining something like

    ostream &(fstream::*ptr)(ios::off_type step, ios::seekdir org) = &seekp;

and then do(fstr->*)ptr(0) the compiler doesn't know anymore whichfunction is actually being called: it merely receives the function'saddress. To solve the compiler's problem the shift (for the location of theofstream object) is now stored in the member pointer itself. That's one reasonwhy the extra data field is needed when using function pointers.

Here is a concrete illustration: first we define 2 structs, each having amember function (all inline, using single line implementations to save somespace):

    struct A    {        int a;    };    struct B    {        int b;        void bfun() {}    };

Then we define C, which is derived from both A (first) and B (next)(comparable tofstream, which embedsifstream andofstream):

    struct C: public A, public B    {};

Next, inmain we define objects of two different unions and assign theaddress ofB::bfun to theirptr fields, butBPTR.ptr looks at itas a member in thestruct B world, whileCPTR.ptr looks at it as amember in thestruct C world.

Once the unions' pointer fields have been assigned their value[] arrays areused to display the content of the ptr fields (see below):

    int main()    {        union BPTR        {            void (B::*ptr)();            unsigned long value[2];        };        BPTR bp;        bp.ptr = &B::bfun;        cout << hex << bp.value[0] << ' ' << bp.value[1] << dec << '\n';        union CPTR        {            void (C::*ptr)();            unsigned long value[2];        };        CPTR cp;        cp.ptr = &C::bfun;        cout << hex << cp.value[0] << ' ' << cp.value[1] << dec << '\n';    }

When this program is run, we see

    400b0c 0    400b0c 4

(your address values (the first ones on the two lines) may differ). Notethat the functions' addresses are the same, but since in the C world the Bobject lives beyond the A object, and the A object is 4 bytes large, we mustadd 4 to the value of the `this' pointer when calling the function from a Cobject. That's exactly what the shift value in the pointer's second field istelling the compiler.




[8]ページ先頭

©2009-2025 Movatter.jp