荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: jjksam ([==面壁大师==]), 信区: Program
标  题: C++ FAQ (part 13 of 14)
发信站: 荔园晨风BBS站 (Mon Jul  1 14:36:23 2002), 转信


From: cline@parashift.com
Sender: cline@parashift.com
Newsgroups: comp.lang.c++,comp.answers,news.answers,alt.comp.lang.learn.c-c++
Subject: C++ FAQ (part 13 of 14)
Summary: Please read this before posting to comp.lang.c++
Followup-To: comp.lang.c++
Reply-To: cline@parashift.com (Marshall Cline)
Distribution: world
Approved: news-answers-request@mit.edu
Expires: +1 month

Archive-name: C++-faq/part13
Posting-Frequency: monthly
Last-modified: Jun 17, 2002
URL: http://www.parashift.com/c++-faq-lite/

AUTHOR: Marshall Cline / cline@parashift.com / 972-931-9470

COPYRIGHT: This posting is part of "C++ FAQ Lite."  The entire "C++ FAQ Lite"
document is Copyright(C)1991-2002 Marshall Cline, Ph.D., cline@parashift.com.
All rights reserved.  Copying is permitted only under designated situations.
For details, see section [1].

NO WARRANTY: THIS WORK IS PROVIDED ON AN "AS IS" BASIS.  THE AUTHOR PROVIDES
NO
WARRANTY WHATSOEVER, EITHER EXPRESS OR IMPLIED, REGARDING THE WORK, INCLUDING
WARRANTIES WITH RESPECT TO ITS MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
PURPOSE.

C++-FAQ-Lite != C++-FAQ-Book: This document, C++ FAQ Lite, is not the same as
the C++ FAQ Book.  The book (C++ FAQs, Cline and Lomow, Addison-Wesley) is
500%
larger than this document, and is available in bookstores.  For details, see
section [3].

==============================================================================

SECTION [31]: How to mix C and C++


[31.1] What do I need to know when mixing C and C++ code?

There are several caveats:
 * You must use your C++ compiler when compiling main() (e.g., for static
   initialization)
 * Your C++ compiler should direct the linking process (e.g., so it can get
its
   special libraries)
 * Your C and C++ compilers probably need to come from same vendor and have
   compatible versions (e.g., so they have the same calling conventions)

In addition, you'll need to read the rest of this section to find out how to
make your C functions callable by C++ and/or your C++ functions callable by C.

BTW there is another way to handle this whole thing: compile all your code
(even your C-style code) using a C++ compiler.  That pretty much eliminates
the
need to mix C and C++, plus it will cause you to be more careful (and possibly
--hopefully!-- discover some bugs) in your C-style code.  The down-side is
that
you'll need to update your C-style code in certain ways, basically because the
C++ compiler is more careful/picky than your C compiler[6.10].  The point is
that the effort required to clean up your C-style code may be less than the
effort required to mix C and C++, and as a bonus you get cleaned up C-style
code.  Obviously you don't have much of a choice if you're not able to alter
your C-style code (e.g., if it's from a third-party).

==============================================================================

[31.2] How can I include a standard C header file in my C++ code?

To #include a standard header file (such as <cstdio>), you don't have to do
anything unusual.  E.g.,

 // This is C++ code

 #include <cstdio>                // Nothing unusual in #include line

 int main()
 {
   std::printf("Hello world\n");  // Nothing unusual in the call either
 }

If you think the std:: part of the std::printf() call is unusual, then the
best
thing to do is "get over it." In other words, it's the standard way to use
names in the standard library, so you might as well start getting used to it
now.

However if you are compiling C code using your C++ compiler, you don't want to
have to tweak all these calls from printf() to std::printf().  Fortunately in
this case the C code will use the old-style header <stdio.h> rather than the
new-style header <cstdio>, and the magic of namespaces will take care of
everything else:

 /* This is C code that I'm compiling using a C++ compiler */

 #include <stdio.h>          /* Nothing unusual in #include line */

 int main()
 {
   printf("Hello world\n");  /* Nothing unusual in the call either */
 }

Final comment: if you have C headers that are not part of the standard library,

we have somewhat different guidelines for you.  There are two cases: either
you
can't change the header[31.3], or you can change the header[31.4].

==============================================================================

[31.3] How can I include a non-system C header file in my C++ code? [UPDATED!]

[Recently fixed a grammatical error in the first paragraph thanks to Randy
Sherman (in 5/02).]

If you are including a C header file that isn't provided by the system, you
may
need to wrap the #include line in an extern C { /*...*/ } construct.  This
tells the C++ compiler that the functions declared in the header file are C
functions.

 // This is C++ code

 extern "C" {
   // Get declaration for f(int i, char c, float x)
   #include "my-C-code.h"
 }

 int main()
 {
   f(7, 'x', 3.14);   // Note: nothing unusual in the call
 }

Note: Somewhat different guidelines apply for C headers provided by the system
(such as <cstdio>)[31.2] and for C headers that you can change[31.4].

==============================================================================

[31.4] How can I modify my own C header files so it's easier to #include them
       in C++ code?

If you are including a C header file that isn't provided by the system, and if
you are able to change the C header, you should strongly consider adding the
extern C {...} logic inside the header to make it easier for C++ users to
#include it into their C++ code.  Since a C compiler won't understand the
extern C construct, you must wrap the extern C { and } lines in an #ifdef so
they won't be seen by normal C compilers.

Step #1: Put the following lines at the very top of your C header file (note:
the symbol __cplusplus is #defined if/only-if the compiler is a C++ compiler):

 #ifdef __cplusplus
 extern "C" {
 #endif

Step #2: Put the following lines at the very bottom of your C header file:

 #ifdef __cplusplus
 }
 #endif

Now you can #include your C header without any extern C nonsense in your C++
code:

 // This is C++ code

 // Get declaration for f(int i, char c, float x)
 #include "my-C-code.h"   // Note: nothing unusual in #include line

 int main()
 {
   f(7, 'x', 3.14);       // Note: nothing unusual in the call
 }

Note: Somewhat different guidelines apply for C headers provided by the system
(such as <cstdio>)[31.2] and for C headers that you can't change[31.3].

Note: #define macros are evil[6.14] in 4 different ways: evil#1[9.3],
evil#2[36.2], evil#3[36.3], and evil#4[36.4].  But they're still useful
sometimes.  Just wash your hands after using them.

==============================================================================

[31.5] How can I call a non-system C function f(int,char,float) from my C++
       code?

If you have an individual C function that you want to call, and for some
reason
you don't have or don't want to #include a C header file in which that
function
is declared, you can declare the individual C function in your C code using
the
extern C syntax.  Naturally you need to use the full function prototype:

 extern "C" void f(int i, char c, float x);

A block of several C functions can be grouped via braces:

 extern "C" {
   void   f(int i, char c, float x);
   int    g(char* s, const char* s2);
   double sqrtOfSumOfSquares(double a, double b);
 }

After this you simply call the function just as if it was a C++ function:

 int main()
 {
   f(7, 'x', 3.14);   // Note: nothing unusual in the call
 }

==============================================================================

[31.6] How can I create a C++ function f(int,char,float) that is callable by
my
       C code?

The C++ compiler must know that f(int,char,float) is to be called by a C
compiler using the extern C construct[31.3]:

 // This is C++ code

 // Declare f(int,char,float) using extern C:
 extern "C" void f(int i, char c, float x);

 // ...

 // Define f(int,char,float) in some C++ module:
 void f(int i, char c, float x)
 {
   // ...
 }

The extern C line tells the compiler that the external information sent to the
linker should use C calling conventions and name mangling (e.g., preceded by a
single underscore).  Since name overloading isn't supported by C, you can't
make several overloaded functions simultaneously callable by a C program.

==============================================================================

[31.7] Why is the linker giving errors for C/C++ functions being called from
       C++/C functions?

If you didn't get your extern C right, you'll sometimes get linker errors
rather than compiler errors.  This is due to the fact that C++ compilers
usually "mangle" function names (e.g., to support function overloading)
differently than C compilers.

See the previous two FAQs on how to use extern C.

==============================================================================

[31.8] How can I pass an object of a C++ class to/from a C function?

Here's an example (for info on extern C, see the previous two FAQs).

Fred.h:

 /* This header can be read by both C and C++ compilers */
 #ifndef FRED_H
 #define FRED_H

 #ifdef __cplusplus
   class Fred {
   public:
     Fred();
     void wilma(int);
   private:
     int a_;
   };
 #else
   typedef
     struct Fred
       Fred;
 #endif

 #ifdef __cplusplus
 extern "C" {
 #endif

 #if defined(__STDC__) || defined(__cplusplus)
   extern void c_function(Fred*);   /* ANSI C prototypes */
   extern Fred* cplusplus_callback_function(Fred*);
 #else
   extern void c_function();        /* K&R style */
   extern Fred* cplusplus_callback_function();
 #endif

 #ifdef __cplusplus
 }
 #endif

 #endif /*FRED_H*/

Fred.cpp:

 // This is C++ code

 #include "Fred.h"

 Fred::Fred() : a_(0) { }

 void Fred::wilma(int a) { }

 Fred* cplusplus_callback_function(Fred* fred)
 {
   fred->wilma(123);
   return fred;
 }

main.cpp:

 // This is C++ code

 #include "Fred.h"

 int main()
 {
   Fred fred;
   c_function(&fred);
   return 0;
 }

c-function.c:

 /* This is C code */

 #include "Fred.h"

 void c_function(Fred* fred)
 {
   cplusplus_callback_function(fred);
 }

Passing pointers to C++ objects to/from C functions will fail if you pass and
get back something that isn't exactly the same pointer.  For example, don't
pass a base class pointer and receive back a derived class pointer, since your
C compiler won't understand the pointer conversions necessary to handle
multiple and/or virtual inheritance.

==============================================================================

[31.9] Can my C function directly access data in an object of a C++ class?

Sometimes.

(For basic info on passing C++ objects to/from C functions, read the previous
FAQ).

You can safely access a C++ object's data from a C function if the C++ class:
 * Has no virtual[20] functions (including inherited virtual functions)
 * Has all its data in the same access-level section
(private/protected/public)
 * Has no fully-contained subobjects with virtual[20] functions

If the C++ class has any base classes at all (or if any fully contained
subobjects have base classes), accessing the data will technically be
non-portable, since class layout under inheritance isn't imposed by the
language.  However in practice, all C++ compilers do it the same way: the base
class object appears first (in left-to-right order in the event of multiple
inheritance), and member objects follow.

Furthermore, if the class (or any base class) contains any virtual functions,
almost all C++ compliers put a void* into the object either at the location of
the first virtual function or at the very beginning of the object.  Again,
this
is not required by the language, but it is the way "everyone" does it.

If the class has any virtual base classes, it is even more complicated and
less
portable.  One common implementation technique is for objects to contain an
object of the virtual base class (V) last (regardless of where V shows up as a
virtual base class in the inheritance hierarchy).  The rest of the object's
parts appear in the normal order.  Every derived class that has V as a virtual
base class actually has a pointer to the V part of the final object.

==============================================================================

[31.10] Why do I feel like I'm "further from the machine" in C++ as opposed to
        C?

Because you are.

As an OO programming language, C++ allows you to model the problem domain
itself, which allows you to program in the language of the problem domain
rather than in the language of the solution domain.

One of C's great strengths is the fact that it has "no hidden mechanism": what
you see is what you get.  You can read a C program and "see" every clock cycle.

This is not the case in C++; old line C programmers (such as many of us once
were) are often ambivalent (can you say, "hostile"?) about this feature.
However after they've made the transition to OO thinking, they often realize
that although C++ hides some mechanism from the programmer, it also provides a
level of abstraction and economy of expression which lowers maintenance costs
without destroying run-time performance.

Naturally you can write bad code in any language; C++ doesn't guarantee any
particular level of quality, reusability, abstraction, or any other measure of
"goodness."

C++ doesn't try to make it impossible for bad programmers to write bad
programs; it enables reasonable developers to create superior software.

==============================================================================

SECTION [32]: Pointers to member functions


[32.1] Is the type of "pointer-to-member-function" different from
       "pointer-to-function"?

Yep.

Consider the following function:

 int f(char a, float b);

The type of this function is different depending on whether it is an ordinary
function or a non-static member function of some class:
 * Its type is "int (*)(char,float)" if an ordinary function
 * Its type is "int (Fred::*)(char,float)" if a non-static member function of
   class Fred

Note: if it's a static member function of class Fred, its type is the same as
if it was an ordinary function: "int (*)(char,float)".

==============================================================================

[32.2] How do I pass a pointer to member function to a signal handler, X event
       callback, etc?

Don't.

Because a member function is meaningless without an object to invoke it on,
you
can't do this directly (if The X Windows System was rewritten in C++, it would
probably pass references to objects around, not just pointers to functions;
naturally the objects would embody the required function and probably a whole
lot more).

As a patch for existing software, use a top-level (non-member) function as a
wrapper which takes an object obtained through some other technique (held in a
global, perhaps).  The top-level function would apply the desired member
function against the global object.

E.g., suppose you want to call Fred::memberFunction() on interrupt:

 class Fred {
 public:
   void memberFunction();
   static void staticMemberFunction();  // A static member function can handle
it
   // ...
 };

 // Wrapper function uses a global to remember the object:
 Fred* object_which_will_handle_signal;
 void Fred_memberFunction_wrapper()
 {
   object_which_will_handle_signal->memberFunction();
 }

 int main()
 {
   /* signal(SIGINT, Fred::memberFunction); */   // Can NOT do this
   signal(SIGINT, Fred_memberFunction_wrapper);  // OK
   signal(SIGINT, Fred::staticMemberFunction);   // Also OK
 }

Note: static member functions do not require an actual object to be invoked,
so
pointers-to-static-member-functions are type compatible with regular
pointers-to-functions.

==============================================================================

[32.3] Why do I keep getting compile errors (type mismatch) when I try to use
a
       member function as an interrupt service routine?

This is a special case of the previous two questions, therefore read the
previous two answers first.

Non-static member functions have a hidden parameter that corresponds to the
this pointer.  The this pointer points to the instance data for the object.
The interrupt hardware/firmware in the system is not capable of providing the
this pointer argument.  You must use "normal" functions (non class members) or
static member functions as interrupt service routines.

One possible solution is to use a static member as the interrupt service
routine and have that function look somewhere to find the instance/member pair
that should be called on interrupt.  Thus the effect is that a member function
is invoked on an interrupt, but for technical reasons you need to call an
intermediate function first.

==============================================================================

[32.4] Why am I having trouble taking the address of a C++ function?

This is a corollary to the previous FAQ.

Long answer: In C++, member functions have an implicit parameter which points
to the object (the this pointer inside the member function).  Normal C
functions can be thought of as having a different calling convention from
member functions, so the types of their pointers (pointer-to-member-function
vs. pointer-to-function) are different and incompatible.  C++ introduces a new
type of pointer, called a pointer-to-member, which can be invoked only by
providing an object.

NOTE: do not attempt to "cast" a pointer-to-member-function into a
pointer-to-function; the result is undefined and probably disastrous.  E.g., a
pointer-to-member-function is not required to contain the machine address of
the appropriate function.  As was said in the last example, if you have a
pointer to a regular C function, use either a top-level (non-member) function,
or a static (class) member function.

==============================================================================

[32.5] How can I avoid syntax errors when calling a member function using a
       pointer-to-member-function?

Two things: (1) use a typedef, and (2) use a #define macro.

Here's the way you create the typedef:

 class Fred {
 public:
   int f(char x, float y);
   int g(char x, float y);
   int h(char x, float y);
   int i(char x, float y);
   // ...
 };

 // FredMemberFn points to a member of Fred that takes (char,float)
 typedef  int (Fred::*FredMemberFn)(char x, float y);

Here's the way you create the #define macro (normally I dislike #define
macros[9.3], but this is one of those rare cases where they actually improve
the readability and writability of your code):

 #define callMemberFunction(object,ptrToMember)  ((object).*(ptrToMember))

Here's how you use these features:

 void userCode(Fred& fred, FredMemberFn memFn)
 {
   callMemberFunction(fred,memFn)('x', 3.14);
   // Would normally be: (fred.*memFn)('x', 3.14);
 }

I strongly recommend these features.  In the real world, member function
invocations are a lot more complex than the simple example just given, and the
difference in readability and writability is significant.  comp.lang.c++ has
had to endure hundreds and hundreds of postings from confused programmers who
couldn't quite get the syntax right.  Almost all these errors would have
vanished had they used these features.

Note: #define macros are evil[6.14] in 4 different ways: evil#1[9.3],
evil#2[36.2], evil#3[36.3], and evil#4[36.4].  But they're still useful
sometimes.  But you should still feel a vague sense of shame after using them.

==============================================================================

[32.6] How do I create and use an array of pointers to member functions?

Use the usual typedef and #define macro[32.5] and you're 90% done.

First, use a typedef:

 class Fred {
 public:
   int f(char x, float y);
   int g(char x, float y);
   int h(char x, float y);
   int i(char x, float y);
   // ...
 };

 // FredMemberFn points to a member of Fred that takes (char,float)
 typedef  int (Fred::*FredMemberFn)(char x, float y);

That makes the array of pointers-to-member-functions straightforward:

 FredMemberFn a[] = { &Fred::f, &Fred::g, &Fred::h, &Fred::i };

Second, use the callMemberFunction macro:

 #define callMemberFunction(object,ptrToMember)  ((object).*(ptrToMember))

That makes calling one of the member functions on object "fred"
straightforward:

 void userCode(Fred& fred, int memberFunctionNum)
 {
   // Assume memberFunctionNum is between 0 and 3 inclusive:
   callMemberFunction(fred, a[memberFunctionNum]) ('x', 3.14);
 }

Note: #define macros are evil[6.14] in 4 different ways: evil#1[9.3],
evil#2[36.2], evil#3[36.3], and evil#4[36.4].  But they're still useful
sometimes.  Feel ashamed, feel guilty, but when an evil construct like a macro
improves your software, use it[6.14].

==============================================================================

SECTION [33]: Container classes and templates


[33.1] Why should I use container classes rather than simple arrays?

Because arrays are evil[6.14].

Let's assume the best case scenario: you're an experienced C programmer, which
almost by definition means you're pretty good at working with arrays.  You
know
you can handle the complexity; you've done it for years.  And you're smart --
the smartest on the team -- the smartest in the whole company.  But even given
all that, please read this entire FAQ and think very carefully about it before
you go into "business as usual" mode.

Fundamentally it boils down to this simple fact: C++ is not C.  That means
(this might be painful for you!!) you'll need to set aside some of your hard
earned wisdom from your vast experience in C.  The two languages simply are
different.  The "best" way to do something in C is not always the same as the
"best" way to do it in C++.  If you really want to program in C, please do
yourself a favor and program in C.  But if you want to be really good at C++,
then learn the C++ ways of doing things.  You may be a C guru, but if you're
just learning C++, you're just learning C++ -- you're a newbie.  (Ouch; I know
that had to hurt.  Sorry.)

Here's what you need to realize about containers vs. arrays:

 1. Container classes make programmers more productive.  So if you insist on
    using arrays while those around are willing to use container classes,
    you'll probably be less productive than they are (even if you're smarter
    and more experienced than they are!).

 2. Container classes let programmers write more robust code.  So if you
insist
    on using arrays while those around are willing to use container classes,
    your code will probably have more bugs than their code (even if you're
    smarter and more experienced).

 3. And if you're so smart and so experienced that you can use arrays as fast
    and as safe as they can use container classes, someone else will probably
    end up maintaining your code and they'll probably introduce bugs.  Or
    worse, you'll be the only one who can maintain your code so management
will
    yank you from development and move you into a full-time maintenance role
--
    just what you always wanted!

Here are some specific problems with arrays:

 1. Subscripts don't get checked to see if they are out of bounds.  (Note that
    some container classes, such as std::vector, have methods to access
    elements with or without bounds checking on subscripts.)

 2. Arrays often require you to allocate memory from the heap (see below for
    examples), in which case you must manually make sure the allocation is
    eventually deleted (even when someone throws an exception).  When you use
    container classes, this memory management is handled automatically, but
    when you use arrays, you have to manually write a bunch of code (and
    unfortunately that code is often subtle and tricky[17.5]) to deal with
    this.  For example, in addition to writing the code that destroys all the
    objects and deletes the memory, arrays often also force you you to write
an
    extra try block with a catch clause that destroys all the objects, deletes
    the memory, then re-throws the exception.  This is a real pain in the neck,

    as shown here[16.15].  When using container classes, things are much
    easier[16.16].

 3. You can't insert an element into the middle of the array, or even add one
    at the end, unless you allocate the array via the heap, and even then you
    must allocate a new array and copy the elements.

 4. Container classes give you the choice of passing them by reference or by
    value, but arrays do not give you that choice: they are always passed by
    reference.  If you want to simulate pass-by-value with an array, you have
    to manually write code that explicitly copies the array's elements
    (possibly allocating from the heap), along with code to clean up the copy
    when you're done with it.  All this is handled automatically for you if
you
    use a container class.

 5. If your function has a non-static local array (i.e., an "auto" array), you
    cannot return that array, whereas the same is not true for objects of
    container classes.

Here are some things to think about when using containers:

 1. Different C++ containers have different strengths and weaknesses, but for
    any given job there's usually one of them that is better -- clearer, safer,

    easier/cheaper to maintain, and often more efficient -- than an array.
For
    instance,
   - You might consider a std::map instead of manually writing code for a
     lookup table.
   - A std::map might also be used for a sparse array or sparse matrix.
   - A std::vector is the most array-like of the standard container classes,
     but it also offers various extra features such as bounds checking via the
     at() member function, insertions/removals of elements, automatic memory
     management even if someone throws an exception, ability to be passed both
     by reference and by value, etc.
   - A std::string is almost always better than an array of char[17.5] (you
can
     think of a std::string as a "container class" for the sake of this
     discussion).

 2. Container classes aren't best for everything, and sometimes you may need
to
    use arrays.  But that should be very rare, and if/when it happens:
   - Please design your container class's public interface in such a way that
     the code that uses the container class is unaware of the fact that there
     is an array inside.
   - The goal is to "bury" the array inside a container class.  In other words,

     make sure there is a very small number of lines of code that directly
     touch the array (just your own methods of your container class) so
     everyone else (the users of your container class) can write code that
     doesn't depend on there being an array inside your container class.

To net this out, arrays really are evil[6.14].  You may not think so if you're
new to C++.  But after you write a big pile of code that uses arrays
(especially if you make your code leak-proof and exception-safe), you'll learn
-- the hard way.  Or you'll learn the easy way by believing those who've
already done things like that.  The choice is yours.

==============================================================================

[33.2] How can I make a perl-like associative array in C++?

Use the standard class template std::map<Key,Val>:

 #include <string>
 #include <map>
 #include <iostream>

 int main()
 {
   // age is a map from string to int
   std::map<std::string, int, std::less<std::string> >  age;

   age["Fred"] = 42;                     // Fred is 42 years old
   age["Barney"] = 37;                   // Barney is 37

   if (todayIsFredsBirthday())           // On Fred's birthday,
     ++ age["Fred"];                     // increment Fred's age

   std::cout << "Fred is " << age["Fred"] << " years old\n";
 }

==============================================================================

[33.3] How can I build a <favorite container> of objects of different types?

You can't, but you can fake it pretty well.  In C/C++ all arrays are
homogeneous (i.e., the elements are all the same type).  However, with an
extra
layer of indirection you can give the appearance of a heterogeneous container
(a heterogeneous container is a container where the contained objects are of
different types).

There are two cases with heterogeneous containers.

The first case occurs when all objects you want to store in a container are
publicly derived from a common base class.  You can then declare/define your
container to hold pointers to the base class.  You indirectly store a derived
class object in a container by storing the object's address as an element in
the container.  You can then access objects in the container indirectly
through
the pointers (enjoying polymorphic behavior).  If you need to know the exact
type of the object in the container you can use dynamic_cast<> or typeid().
You'll probably need the Virtual Constructor Idiom[20.6] to copy a container
of
disparate object types.  The downside of this approach is that it makes memory
management a little more problematic (who "owns" the pointed-to objects? if
you
delete these pointed-to objects when you destroy the container, how can you
guarantee that no one else has a copy of one of these pointers? if you don't
delete these pointed-to objects when you destroy the container, how can you be
sure that someone else will eventually do the deleteing?).  It also makes
copying the container more complex (may actually break the container's copying
functions since you don't want to copy the pointers, at least not when the
container "owns" the pointed-to objects).

The second case occurs when the object types are disjoint -- they do not share
a common base class.  The approach here is to use a handle class.  The
container is a container of handle objects (by value or by pointer, your
choice; by value is easier).  Each handle object knows how to "hold on to"
(i.e. ,maintain a pointer to) one of the objects you want to put in the
container.  You can use either a single handle class with several different
types of pointers as instance data, or a hierarchy of handle classes that
shadow the various types you wish to contain (requires the container be of
handle base class pointers).  The downside of this approach is that it opens
up
the handle class(es) to maintenance every time you change the set of types
that
can be contained.  The benefit is that you can use the handle class(es) to
encapsulate most of the ugliness of memory management and object lifetime.
Thus using handle objects may be beneficial even in the first case.

==============================================================================

[33.4] How can I insert/access/change elements from a linked
       list/hashtable/etc?

The most important thing to remember is this: don't roll your own from scratch
unless there is a compelling reason to do so.  In other words, instead of
creating your own list or hashtable, use one of the standard class templates
such as std::vector<T> or std::list<T> or whatever.

Assuming you have a compelling reason to build your own container, here's how
to handle inserting (or accessing, changing, etc.) the elements.

To make the discussion concrete, I'll discuss how to insert an element into a
linked list.  This example is just complex enough that it generalizes pretty
well to things like vectors, hash tables, binary trees, etc.

A linked list makes it easy insert an element before the first or after the
last element of the list, but limiting ourselves to these would produce a
library that is too weak (a weak library is almost worse than no library).
This answer will be a lot to swallow for novice C++'ers, so I'll give a couple
of options.  The first option is easiest; the second and third are better.

 1. Empower the List with a "current location," and member functions such as
    advance(), backup(), atEnd(), atBegin(), getCurrElem(), setCurrElem(Elem),
    insertElem(Elem), and removeElem().  Although this works in small examples,

    the notion of a current position makes it difficult to access elements at
    two or more positions within the list (e.g., "for all pairs x,y do the
    following...").

 2. Remove the above member functions from List itself, and move them to a
    separate class, ListPosition.  ListPosition would act as a "current
    position" within a list.  This allows multiple positions within the same
    list.  ListPosition would be a friend[14] of class List, so List can hide
    its innards from the outside world (else the innards of List would have to
    be publicized via public member functions in List).  Note: ListPosition
can
    use operator overloading for things like advance() and backup(), since
    operator overloading is syntactic sugar for normal member functions.

 3. Consider the entire iteration as an atomic event, and create a class
    template that embodies this event.  This enhances performance by allowing
    the public access member functions (which may be virtual[20] functions) to
    be avoided during the access, and this access often occurs within an inner
    loop.  Unfortunately the class template will increase the size of your
    object code, since templates gain speed by duplicating code.  For more,
see
    [Koenig, "Templates as interfaces," JOOP, 4, 5 (Sept 91)], and [Stroustrup,

    "The C++ Programming Language Third Edition," under "Comparator"].

==============================================================================

[33.5] What's the idea behind templates?

A template is a cookie-cutter that specifies how to cut cookies that all look
pretty much the same (although the cookies can be made of various kinds of
dough, they'll all have the same basic shape).  In the same way, a class
template is a cookie cutter for a description of how to build a family of
classes that all look basically the same, and a function template describes
how
to build a family of similar looking functions.

Class templates are often used to build type safe containers (although this
only scratches the surface for how they can be used).

==============================================================================

[33.6] What's the syntax / semantics for a "class template"?

Consider a container class Array that acts like an array of integers:

 // This would go into a header file such as "Array.h"
 class Array {
 public:
   Array(int len=10)                  : len_(len), data_(new int[len]) { }
  ~Array()                            { delete[] data_; }
   int len() const                    { return len_;     }
   const int& operator[](int i) const { return data_[check(i)]; }
         int& operator[](int i)       { return data_[check(i)]; }
   Array(const Array&);
   Array& operator= (const Array&);
 private:
   int  len_;
   int* data_;
   int  check(int i) const
     { if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);
       return i; }
 };

Repeating the above over and over for Array of float, of char, of std::string,
of Array-of-std::string, etc, will become tedious.

 // This would go into a header file such as "Array.h"
 template<class T>
 class Array {
 public:
   Array(int len=10)                : len_(len), data_(new T[len]) { }
  ~Array()                          { delete[] data_; }
   int len() const                  { return len_;     }
   const T& operator[](int i) const { return data_[check(i)]; }
         T& operator[](int i)       { return data_[check(i)]; }
   Array(const Array<T>&);
   Array<T>& operator= (const Array<T>&);
 private:
   int len_;
   T*  data_;
   int check(int i) const
     { if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);
       return i; }
 };

Unlike template functions[33.7], template classes (instantiations of class
templates) need to be explicit about the parameters over which they are
instantiating:

 int main()
 {
   Array<int>           ai;
   Array<float>         af;
   Array<char*>         ac;
   Array<std::string>   as;
   Array< Array<int> >  aai;
 }

Note the space between the two >'s in the last example.  Without this space,
the compiler would see a >> (right-shift) token instead of two >'s.

==============================================================================

[33.7] What's the syntax / semantics for a "function template"?

Consider this function that swaps its two integer arguments:

 void swap(int& x, int& y)
 {
   int tmp = x;
   x = y;
   y = tmp;
 }

If we also had to swap floats, longs, Strings, Sets, and FileSystems, we'd get
pretty tired of coding lines that look almost identical except for the type.
Mindless repetition is an ideal job for a computer, hence a function template:

 template<class T>
 void swap(T& x, T& y)
 {
   T tmp = x;
   x = y;
   y = tmp;
 }

Every time we used swap() with a given pair of types, the compiler will go to
the above definition and will create yet another "template function" as an
instantiation of the above.  E.g.,

 int main()
 {
   int         i,j;  /*...*/  swap(i,j);  // Instantiates a swap for int
   float       a,b;  /*...*/  swap(a,b);  // Instantiates a swap for float
   char        c,d;  /*...*/  swap(c,d);  // Instantiates a swap for char
   std::string s,t;  /*...*/  swap(s,t);  // Instantiates a swap for std::
string
 }

Note: A "template function" is the instantiation of a "function template".

==============================================================================

[33.8] How do I explicitly select which version of a function template should
       get called?

When you call a function template, the compiler tries to deduce the template
type.  Most of the time it can do that successfully, but every once in a while
you may want to help the compiler deduce the right type -- either because it
cannot deduce the type at all, or perhaps because it would deduce the wrong
type.

For example, you might be calling a function template that doesn't have any
parameters of its template argument types, or you might want to force the
compiler to do certain promotions on the arguments before selecting the
correct
function template.  In these cases you'll need to explicitly tell the compiler
which instantiation of the function template should be called.

Here is a sample function template where the template parameter T does not
appear in the function's parameter list.  In this case the compiler cannot
deduce the template parameter types when the function is called.

 template<class T>
 void f()
 {
   // ...
 }

To call this function with T being an int or a std::string, you could say:

 #include <string>

 void sample()
 {
   f<int>();          // type T will be int in this call
   f<std::string>();  // type T will be std::string in this call
 }

Here is another function whose template parameters appear in the function's
list of formal parameters (that is, the compiler can deduce the template type
from the actual arguments):

 template<class T>
 void g(T x)
 {
   // ...
 }

Now if you want to force the actual arguments to be promoted before the
compiler deduces the template type, you can use the above technique.  E.g., if
you simply called g(42) you would get g<int>(42), but if you wanted to pass 42
to g<long>(), you could say this: g<long>(42).  (Of course you could also
promote the parameter explicitly, such as either g(long(42)) or even g(42L),
but that ruins the example.)

Similarly if you said g("xyz") you'd end up calling g<char*>(char*), but if
you
wanted to call the std::string version of g<>() you could say
g<std::string>("xyz").  (Again you could also promote the argument, such as
g(std::string("xyz")), but that's another story.)

==============================================================================

[33.9] What is a "parameterized type"?

Another way to say, "class templates."

A parameterized type is a type that is parameterized over another type or some
value.  List<int> is a type (List) parameterized over another type (int).

==============================================================================

[33.10] What is "genericity"?

Yet another way to say, "class templates."

Not to be confused with "generality" (which just means avoiding solutions
which
are overly specific), "genericity" means class templates.

==============================================================================

[33.11] Why can't I separate the definition of my templates class from it's
        declaration and put it inside a .cpp file?

In order to understand why things are the way they are, you must accept a few
facts:

 1. A template is not a class or a function.  A template is a "pattern" that
    the compiler uses to generate a family of classes[33.6] or functions[33.7].


 2. In order for the compiler to generate the code, it must see both the
    template definition (not just declaration) and the specific types/whatever
    used to "fill in" the template.  For example, if you're trying to use a
    Foo<int>, the compiler must see both the Foo template and the fact that
    you're trying to make a specific Foo<int>.

 3. Your compiler probably doesn't remember the details of one .cpp file while
    it is compiling another .cpp file.  It could, but most do not and if you
    are reading this FAQ, it almost definitely does not.  BTW this is called
    the "separate compilation model."

Now assuming you accept those facts, let's work an example to show why things
are the way they are.  Suppose you have a template Foo defined like this:

 template<class T>
 class Foo {
 public:
   Foo();
   void someMethod(T x);
 private:
   T x;
 };

Along with similar definitions for the member functions:

 template<class T>
 Foo<T>::Foo()
 {
   ...
 }

 template<class T>
 void Foo<T>::someMethod(T x)
 {
   ...
 }

Now suppose you have some code in file Bar.cpp that uses Foo<int>:

 // Bar.cpp

 void blah_blah_blah()
 {
   ...
   Foo<int> f;
   f.someMethod(5);
   ...
 }

Clearly somebody somewhere is going to have to use the "pattern" for the
constructor definition and for the someMethod() definition and instantiate
those when T is actually int.  But if you had put the definition of the
constructor and someMethod() into file Foo.cpp, the compiler would see the
template code when it compiled Foo.cpp and it would see Foo<int> when it
compiled Bar.cpp, but there would never be a time when it saw both the
template
code and Foo<int>.  So by rule #2 above, it could never generate the code for
Foo<int>::someMethod().

The simplest approach to this is to add the definitions for all the methods
(not just the inline methods) in Foo.hpp.  There are other approaches as well,
and some of these other approaches result in smaller executables at the
expense
of a little ease-of-use.

A note to the experts: I have obviously made several simplifications above.
This was intentional so please don't complain too loudly.  For example, if you
know enough to complain about my simplifications (e.g., if you know the
difference between a .cpp file and a compilation unit, the difference between
a
class template and a template class, and the fact that templates really aren't
just glorified macros), then you didn't need to read this answer in the first
place -- this particular question/answer wasn't aimed at you.  Bottom line: I
simplified things so newbies would "get it," even if doing so offends some
experts.

==============================================================================

SECTION [34]: Class libraries


[34.1] What is the "STL"?

STL ("Standard Templates Library") is a library that consists mainly of (very
efficient) container classes, along with some iterators and algorithms to work
with the contents of these containers.

Technically speaking the term "STL" is no longer meaningful since the classes
provided by the STL have been fully integrated into the standard library,
along
with other standard classes like std::ostream, etc.  Nonetheless many people
still refer to the STL as if it was a separate thing, so you might as well get
used to hearing that term.

==============================================================================

[34.2] Where can I get a copy of "STL"?

Since the classes that were part of the STL[34.1] have become part of the
standard library, your compiler should provide these classes.  If your
compiler
doesn't include these standard classes, either get an updated version of your
compiler or download a copy of the STL classes from one of the following:
 * An STL site: ftp://ftp.cs.rpi.edu/pub/stl
 * STL HP official site: ftp://butler.hpl.hp.com/stl/
 * Mirror site in Europe: www.maths.warwick.ac.uk/ftp/mirrors/c++/stl/
 * STL code alternate: ftp://ftp.cs.rpi.edu/stl
 * The SGI implementation: www.sgi.com/Technology/STL/
 * STLport: www.stlport.org

STL hacks for GCC-2.6.3 are part of the GNU libg++ package 2.6.2.1 or later
(and they may be in an earlier version as well).  Thanks to Mike Lindner.

Also you may as well get used to some people using "STL" to include the
standard string header, "<string>", and others objecting to that usage.

==============================================================================

[34.3] How can I find a Fred object in an STL container of Fred* such as
       std::vector<Fred*>?

STL functions such as std::find_if() help you find a T element in a container
of T's.  But if you have a container of pointers such as std::vector<Fred*>,
these functions will enable you to find an element that matches a given Fred*
pointer, but they don't let you find an element that matches a given Fred
object.

The solution is to use an optional parameter that specifies the "match"
function.  The following class template lets you compare the objects on the
other end of the dereferenced pointers.

 template<class T>
 class DereferencedEqual {
 public:
   DereferencedEqual(const T* p) : p_(p) { }
   bool operator() (const T* p2) const { return *p_ == *p2; }
 private:
   const T* p_;
 };

Now you can use this template to find an appropriate Fred object:

 void userCode(std::vector<Fred*> v, const Fred& match)
 {
   std::find_if(v.begin(), v.end(), DereferencedEqual<Fred>(&match));
   // ...
 }

==============================================================================

[34.4] Where can I get help on how to use STL?

The STL FAQ: ftp://butler.hpl.hp.com/stl/stl.faq

Kenny Zalewski's STL guide: www.cs.rpi.edu/projects/STL/htdocs/stl.html

Mumit's STL Newbie's guide:
www.xraylith.wisc.edu/~khan/software/stl/STL.newbie.html

More info is in the book section[27.7].

==============================================================================

[34.5] How can you tell if you have a dynamically typed C++ class library?

 * Hint #1: when everything is derived from a single root class, usually
   Object.
 * Hint #2: when the container classes (List, Stack, Set, etc) are
   non-templates.
 * Hint #3: when the container classes (List, Stack, Set, etc) insert/extract
   elements as pointers to Object.  This lets you put an Apple into such a
   container, but when you get it out, the compiler knows only that it is
   derived from Object, so you have to use a pointer cast to convert it back
to
   an Apple*; and you'd better pray a lot that it really is an Apple, cause
   your blood is on your own head[26.10]).

You can make the pointer cast "safe" by using dynamic_cast, but this dynamic
testing is just that: dynamic.  This coding style is the essence of dynamic
typing in C++.  You call a function that says "convert this Object into an
Apple or give me NULL if its not an Apple," and you've got dynamic typing: you
don't know what will happen until run-time.

When you use templates to implement your containers, the C++ compiler can
statically validate 90+% of an application's typing information (the figure
"90+%" is apocryphal; some claim they always get 100%, those who need
persistence[36.8] get something less than 100% static type checking).  The
point is: C++ gets genericity from templates, not from inheritance.

==============================================================================

[34.6] What is the NIHCL? Where can I get it?

NIHCL stands for "National-Institute-of-Health's-class-library." It can be
acquired via ftp://128.231.128.7/pub/NIHCL/nihcl-3.0.tar.Z

NIHCL (some people pronounce it "N-I-H-C-L," others pronounce it like
"nickel")
is a C++ translation of the Smalltalk class library[34.5].  There are some
ways
where NIHCL's use of dynamic typing helps (e.g., persistent[36.8] objects).
There are also places where its use of dynamic typing creates tension[29.3]
with the static typing of the C++ language.

==============================================================================

[34.7] Where can I ftp the code that accompanies "Numerical Recipes"?

This software is sold and therefore it would be illegal to provide it on the
net.  However, it's only about $30.

==============================================================================

[34.8] Why is my executable so large?

Many people are surprised by how big executables are, especially if the source
code is trivial.  For example, a simple "hello world" program can generate an
executable that is larger than most people expect (40+K bytes).

One reason executables can be large is that portions of the C++ runtime
library
gets linked with your program. How much gets linked in depends on how much of
it you are using, and on how the implementer split up the library into pieces.
For example, the <iostream> library is quite large, and consists of numerous
classes and virtual[20] functions.  Using any part of it might pull in nearly
all of the <iostream> code as a result of the interdependencies.

You might be able to make your program smaller by using a dynamically-linked
version of the library instead of the static version.

You have to consult your compiler manuals or the vendor's technical support
for
a more detailed answer.

==============================================================================

[34.9] Where can I get tons and tons of more information on C++ class
       libraries?

The C++ Libraries FAQ is maintained by Nikki Locke and is available at
www.trumphurst.com/cpplibs1.html

Also you should check out www.mathtools.net/C++/.  They have a good pile of
stuff organized into (at present) sixty-some categories.

==============================================================================


--
   mm       ★__      __  __ __★______ ______ __  __★
/^(  )^\      █      █  █/    █____ █__█ █∨█
\,(..),/  ▅__█  ▅__█  █\__  ▂__█ █  █ █  █
  V~~V   ▇▆▅▃▁I'm a bat. I'm very bad!^Q^_▃▄▆▇ 你好!^_^欢迎大家到linux

※ 修改:·jjksam 於 Jul  1 14:44:40 修改本文·[FROM: 192.168.0.146]
※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.0.146]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店