|














Am avut 371729 vizite de la lansarea siteului.

|
|
Inapoi
Inainte
Cuprins
Pointers to members
A pointer is a variable that holds the
address of some location. You can change what a pointer selects at runtime, and
the destination of the pointer can be either data or a function. The C++
pointer-to-member follows this same concept, except that what it selects
is a location inside a class. The dilemma here is that a pointer needs an
address, but there is no “address” inside a class; selecting a
member of a class means offsetting into that class. You can’t produce an
actual address until you combine that offset with the starting address of a
particular object. The syntax of pointers to members requires that you select an
object at the same time you’re dereferencing the pointer to
member.
To understand this syntax, consider a
simple structure, with a pointer sp and an object so for this
structure. You can select members with the syntax shown:
//: C11:SimpleStructure.cpp
struct Simple { int a; };
int main() {
Simple so, *sp = &so;
sp->a;
so.a;
} ///:~
Now suppose you have an ordinary pointer
to an integer, ip. To access what ip is pointing to, you
dereference the pointer with a ‘*’:
*ip = 4;
Finally, consider what happens if you
have a pointer that happens to point to something inside a class object, even if
it does in fact represent an offset into the object. To access what it’s
pointing at, you must dereference it with *. But it’s an offset
into an object, so you must also refer to that particular object. Thus, the
* is combined with the object dereference. So the new syntax
becomes –>* for a pointer to an object,
and .* for the object or a reference, like
this:
objectPointer->*pointerToMember = 47;
object.*pointerToMember = 47;
Now, what is the syntax for defining
pointerToMember? Like any pointer, you have to say what type it’s
pointing at, and you use a * in the definition. The only difference is
that you must say what class of objects this pointer-to-member is used with. Of
course, this is accomplished with the name of the class and the scope resolution
operator. Thus,
int ObjectClass::*pointerToMember;
defines a pointer-to-member variable
called pointerToMember that points to any int inside
ObjectClass. You can also initialize the pointer-to-member when you
define it (or at any other time):
int ObjectClass::*pointerToMember = &ObjectClass::a;
There is actually no
“address” of ObjectClass::a because you’re just
referring to the class and not an object of that class. Thus,
&ObjectClass::a can be used only as pointer-to-member
syntax.
Here’s an example that shows how to
create and use pointers to data members:
//: C11:PointerToMemberData.cpp
#include <iostream>
using namespace std;
class Data {
public:
int a, b, c;
void print() const {
cout << "a = " << a << ", b = " << b
<< ", c = " << c << endl;
}
};
int main() {
Data d, *dp = &d;
int Data::*pmInt = &Data::a;
dp->*pmInt = 47;
pmInt = &Data::b;
d.*pmInt = 48;
pmInt = &Data::c;
dp->*pmInt = 49;
dp->print();
} ///:~
Obviously, these are too awkward to use
anywhere except for special cases (which is exactly what they were intended
for).
Also, pointers to members are quite
limited: they can be assigned only to a specific location inside a class. You
could not, for example, increment or compare them as you can with ordinary
pointers.
Functions
A similar exercise produces the
pointer-to-member syntax for member functions. A pointer to a function
(introduced at the end of Chapter 3) is defined like this:
int (*fp)(float);
The parentheses around (*fp) are
necessary to force the compiler to evaluate the definition properly. Without
them this would appear to be a function that returns an int*.
Parentheses also play an important role
when defining and using pointers to member functions. If you have a function
inside a class, you define a pointer to that member function by inserting the
class name and scope resolution operator into an ordinary function pointer
definition:
//: C11:PmemFunDefinition.cpp
class Simple2 {
public:
int f(float) const { return 1; }
};
int (Simple2::*fp)(float) const;
int (Simple2::*fp2)(float) const = &Simple2::f;
int main() {
fp = &Simple2::f;
} ///:~
In the definition for fp2 you can
see that a pointer to member function can also be initialized when it is
created, or at any other time. Unlike non-member functions, the & is
not optional when taking the address of a member function. However, you
can give the function identifier without an argument list, because overload
resolution can be determined by the type of the pointer to member.
An example
The value of a pointer is that you can
change what it points to at runtime, which provides an important flexibility in
your programming because through a pointer you can select or change
behavior at runtime. A pointer-to-member is no different; it allows you
to choose a member at runtime. Typically, your classes will only have member
functions publicly visible (data members are usually considered part of the
underlying implementation), so the following example selects member functions at
runtime.
//: C11:PointerToMemberFunction.cpp
#include <iostream>
using namespace std;
class Widget {
public:
void f(int) const { cout << "Widget::f()\n"; }
void g(int) const { cout << "Widget::g()\n"; }
void h(int) const { cout << "Widget::h()\n"; }
void i(int) const { cout << "Widget::i()\n"; }
};
int main() {
Widget w;
Widget* wp = &w;
void (Widget::*pmem)(int) const = &Widget::h;
(w.*pmem)(1);
(wp->*pmem)(2);
} ///:~
Of course, it isn’t particularly
reasonable to expect the casual user to create such complicated expressions. If
the user must directly manipulate a pointer-to-member, then a typedef is
in order. To really clean things up, you can use the pointer-to-member as part
of the internal implementation mechanism. Here’s the preceding example
using a pointer-to-member inside the class. All the user needs to do is
pass a number in to select a
function.[48]
//: C11:PointerToMemberFunction2.cpp
#include <iostream>
using namespace std;
class Widget {
void f(int) const { cout << "Widget::f()\n"; }
void g(int) const { cout << "Widget::g()\n"; }
void h(int) const { cout << "Widget::h()\n"; }
void i(int) const { cout << "Widget::i()\n"; }
enum { cnt = 4 };
void (Widget::*fptr[cnt])(int) const;
public:
Widget() {
fptr[0] = &Widget::f; // Full spec required
fptr[1] = &Widget::g;
fptr[2] = &Widget::h;
fptr[3] = &Widget::i;
}
void select(int i, int j) {
if(i < 0 || i >= cnt) return;
(this->*fptr[i])(j);
}
int count() { return cnt; }
};
int main() {
Widget w;
for(int i = 0; i < w.count(); i++)
w.select(i, 47);
} ///:~
In the class interface and in
main( ), you can see that the entire implementation, including the
functions, has been hidden away. The code must even ask for the
count( ) of functions. This way, the class implementer can change
the quantity of functions in the underlying implementation without affecting the
code where the class is used.
The initialization of the
pointers-to-members in the constructor may seem overspecified. Shouldn’t
you be able to say
fptr[1] = &g;
because the name g occurs in the
member function, which is automatically in the scope of the class? The problem
is this doesn’t conform to the pointer-to-member syntax, which is required
so everyone, especially the compiler, can figure out what’s going on.
Similarly, when the pointer-to-member is dereferenced, it seems
like
(this->*fptr[i])(j);
is also over-specified; this looks
redundant. Again, the syntax requires that a pointer-to-member always be bound
to an object when it is
dereferenced.
Summary
Pointers in C++ are almost identical to
pointers in C, which is good. Otherwise, a lot of C code wouldn’t compile
properly under C++. The only compile-time errors you will produce occur with
dangerous assignments. If these are in fact what are intended, the compile-time
errors can be removed with a simple (and explicit!) cast.
C++ also adds the reference from
Algol and Pascal, which is like a constant pointer that is automatically
dereferenced by the compiler. A reference holds an address, but you treat it
like an object. References are essential for clean syntax with operator
overloading (the subject of the next chapter), but they also add syntactic
convenience for passing and returning objects for ordinary
functions.
The copy-constructor takes a reference to
an existing object of the same type as its argument, and it is used to create a
new object from an existing one. The compiler automatically calls the
copy-constructor when you pass or return an object by value. Although the
compiler will automatically create a copy-constructor for you, if you think one
will be needed for your class, you should always define it yourself to ensure
that the proper behavior occurs. If you don’t want the object passed or
returned by value, you should create a private
copy-constructor.
Pointers-to-members have the same
functionality as ordinary pointers: You can choose a particular region of
storage (data or function) at runtime. Pointers-to-members just happen to work
with class members instead of with global data or functions. You get the
programming flexibility that allows you to change behavior at
runtime.
Exercises
Solutions to selected exercises
can be found in the electronic document The Thinking in C++ Annotated
Solution Guide, available for a small fee from www.BruceEckel.com.
- Turn the “bird &
rock” code fragment at the beginning of this chapter into a C program
(using structs for the data types), and show that it compiles. Now try to
compile it with the C++ compiler and see what
happens.
- Take the
code fragments in the beginning of the section titled “References in
C++” and put them into a main( ). Add statements to print
output so that you can prove to yourself that references are like pointers that
are automatically
dereferenced.
- Write
a program in which you try to (1) Create a reference that is not initialized
when it is created. (2) Change a reference to refer to another object after it
is initialized. (3) Create a NULL
reference.
- Write a
function that takes a pointer argument, modifies what the pointer points to, and
then returns the destination of the pointer as a
reference.
- Create a
class with some member functions, and make that the object that is pointed to by
the argument of Exercise 4. Make the pointer a const and make some of the
member functions const and prove that you can only call the const
member functions inside your function. Make the argument to your function a
reference instead of a
pointer.
- Take the
code fragments at the beginning of the section titled “Pointer
references” and turn them into a
program.
- Create a
function that takes an argument of a reference to a pointer to a pointer and
modifies that argument. In main( ), call the
function.
- Create a
function that takes a char& argument and modifies that argument. In
main( ), print out a char variable, call your function for
that variable, and print it out again to prove to yourself that it has been
changed. How does this affect program
readability?
- Write a
class that has a const member function and a non-const member
function. Write three functions that take an object of that class as an
argument; the first takes it by value, the second by reference, and the third by
const reference. Inside the functions, try to call both member functions
of your class and explain the
results.
- (Somewhat
challenging) Write a simple function that takes an int as an argument,
increments the value, and returns it. In main( ), call your
function. Now discover how your compiler generates assembly code and trace
through the assembly statements so that you understand how arguments are passed
and returned, and how local variables are indexed off the
stack.
- Write a
function that takes as its arguments a char, int, float,
and double. Generate assembly code with your compiler and find the
statements that push the arguments on the stack before a function
call.
- Write a
function that returns a double. Generate assembly code and determine how
the value is
returned.
- Produce
assembly code for PassingBigStructures.cpp. Trace through and demystify
the way your compiler generates code to pass and return large
structures.
- Write a
simple recursive function that decrements its argument and returns zero if the
argument becomes zero, otherwise it calls itself. Generate assembly code for
this function and explain how the way that the assembly code is created by the
compiler supports
recursion.
- Write
code to prove that the compiler automatically synthesizes a copy-constructor if
you don’t create one yourself. Prove that the synthesized copy-constructor
performs a bitcopy of primitive types and calls the copy-constructor of
user-defined
types.
- Write a class
with a copy-constructor that announces itself to cout. Now create a
function that passes an object of your new class in by value and another one
that creates a local object of your new class and returns it by value. Call
these functions to prove to yourself that the copy-constructor is indeed quietly
called when passing and returning objects by value.
- Create a class that
contains a double*. The constructor initializes the double* by
calling new double and assigning a value to the resulting storage from
the constructor argument. The destructor prints the value that’s pointed
to, assigns that value to -1, calls delete for the storage, and then sets
the pointer to zero. Now create a function that takes an object of your class by
value, and call this function in main( ). What happens? Fix the
problem by writing a
copy-constructor.
- Create
a class with a constructor that looks like a copy-constructor, but that has an
extra argument with a default value. Show that this is still used as the
copy-constructor.
- Create
a class with a copy-constructor that announces itself. Make a second class
containing a member object of the first class, but do not create a
copy-constructor. Show that the synthesized copy-constructor in the second class
automatically calls the copy-constructor of the first
class.
- Create a very
simple class, and a function that returns an object of that class by value.
Create a second function that takes a reference to an object of your class. Call
the first function as the argument of the second function, and demonstrate that
the second function must use a const reference as its
argument.
- Create a
simple class without a copy-constructor, and a simple function that takes an
object of that class by value. Now change your class by adding a private
declaration (only) for the copy-constructor. Explain what happens when your
function is
compiled.
- This
exercise creates an alternative to using the copy-constructor. Create a class
X and declare (but don’t define) a private copy-constructor.
Make a public clone( ) function as a const member function
that returns a copy of the object that is created using new. Now write a
function that takes as an argument a const X& and clones a local copy
that can be modified. The drawback to this approach is that you are responsible
for explicitly destroying the cloned object (using delete) when
you’re done with
it.
- Explain
what’s wrong with both Mem.cpp and MemTest.cpp from Chapter
7. Fix the
problem.
- Create a
class containing a double and a print( ) function that prints
the double. In main( ), create pointers to members for both
the data member and the function in your class. Create an object of your class
and a pointer to that object, and manipulate both class elements via your
pointers to members, using both the object and the pointer to the
object.
- Create a
class containing an array of int. Can you index through this array using
a pointer to
member?
- Modify
PmemFunDefinition.cpp by adding an overloaded member function
f( ) (you can determine the argument list that causes the overload).
Now make a second pointer to member, assign it to the overloaded version of
f( ), and call the function through that pointer. How does the
overload resolution happen in this
case?
- Start with
FunctionTable.cpp from Chapter 3. Create a class that contains a
vector of pointers to functions, with add( ) and
remove( ) member functions to add and remove pointers to functions.
Add a run( ) function that moves through the vector and calls
all of the
functions.
- Modify
the above Exercise 27 so that it works with pointers to member functions
instead.
 |
| |
|