home *** CD-ROM | disk | FTP | other *** search
- Path: senator-bedfellow.mit.edu!bloom-beacon.mit.edu!news-out.cwix.com!newsfeed.cwix.com!newsfeed.berkeley.edu!newsfeed.stanford.edu!novia!nntp3.cerf.net!nntp2.cerf.net!news.cerf.net!not-for-mail
- From: mpcline@nic.cerf.net (Marshall Cline)
- Newsgroups: comp.lang.c++,comp.answers,news.answers,alt.comp.lang.learn.c-c++
- Subject: C++ FAQ (part 5 of 10)
- Followup-To: comp.lang.c++
- Date: 29 Feb 2000 20:06:53 GMT
- Organization: ATT Cerfnet
- Lines: 502
- Approved: news-answers-request@mit.edu
- Distribution: world
- Expires: +1 month
- Message-ID: <89h8st$btu$1@news.cerf.net>
- Reply-To: cline@parashift.com (Marshall Cline)
- NNTP-Posting-Host: nic1.san.cerf.net
- X-Trace: news.cerf.net 951854813 12222 192.215.81.88 (29 Feb 2000 20:06:53 GMT)
- X-Complaints-To: abuse@cerf.net
- NNTP-Posting-Date: 29 Feb 2000 20:06:53 GMT
- Summary: Please read this before posting to comp.lang.c++
- Xref: senator-bedfellow.mit.edu comp.lang.c++:453816 comp.answers:39856 news.answers:178215 alt.comp.lang.learn.c-c++:40794
-
- Archive-name: C++-faq/part5
- Posting-Frequency: monthly
- Last-modified: Feb 29, 2000
- URL: http://marshall-cline.home.att.net/cpp-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-2000 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 [14]: Friends
-
-
- [14.1] What is a friend?
-
- Something to allow your class to grant access to another class or function.
-
- Friends can be either functions or other classes. A class grants access
- privileges to its friends. Normally a developer has political and technical
- control over both the friend and member functions of a class (else you may need
- to get permission from the owner of the other pieces when you want to update
- your own class).
-
- ==============================================================================
-
- [14.2] Do friends violate encapsulation?
-
- If they're used properly, they actually enhance encapsulation.
-
- You often need to split a class in half when the two halves will have different
- numbers of instances or different lifetimes. In these cases, the two halves
- usually need direct access to each other (the two halves used to be in the same
- class, so you haven't increased the amount of code that needs direct access to
- a data structure; you've simply reshuffled the code into two classes instead of
- one). The safest way to implement this is to make the two halves friends of
- each other.
-
- If you use friends like just described, you'll keep private things private.
- People who don't understand this often make naive efforts to avoid using
- friendship in situations like the above, and often they actually destroy
- encapsulation. They either use public data (grotesque!), or they make the data
- accessible between the halves via public get() and set() member functions.
- Having a public get() and set() member function for a private datum is OK only
- when the private datum "makes sense" from outside the class (from a user's
- perspective). In many cases, these get()/set() member functions are almost as
- bad as public data: they hide (only) the name of the private datum, but they
- don't hide the existence of the private datum.
-
- Similarly, if you use friend functions as a syntactic variant of a class's
- public: access functions, they don't violate encapsulation any more than a
- member function violates encapsulation. In other words, a class's friends
- don't violate the encapsulation barrier: along with the class's member
- functions, they are the encapsulation barrier.
-
- ==============================================================================
-
- [14.3] What are some advantages/disadvantages of using friend functions?
-
- They provide a degree of freedom in the interface design options.
-
- Member functions and friend functions are equally privileged (100% vested).
- The major difference is that a friend function is called like f(x), while a
- member function is called like x.f(). Thus the ability to choose between
- member functions (x.f()) and friend functions (f(x)) allows a designer to
- select the syntax that is deemed most readable, which lowers maintenance costs.
-
- The major disadvantage of friend functions is that they require an extra line
- of code when you want dynamic binding. To get the effect of a virtual friend,
- the friend function should call a hidden (usually protected:) virtual[20]
- member function. This is called the Virtual Friend Function Idiom[15.8]. For
- example:
-
- class Base {
- public:
- friend void f(Base& b);
- // ...
- protected:
- virtual void do_f();
- // ...
- };
-
- inline void f(Base& b)
- {
- b.do_f();
- }
-
- class Derived : public Base {
- public:
- // ...
- protected:
- virtual void do_f(); // "Override" the behavior of f(Base& b)
- // ...
- };
-
- void userCode(Base& b)
- {
- f(b);
- }
-
- The statement f(b) in userCode(Base&) will invoke b.do_f(), which is
- virtual[20]. This means that Derived::do_f() will get control if b is actually
- a object of class Derived. Note that Derived overrides the behavior of the
- protected: virtual[20] member function do_f(); it does not have its own
- variation of the friend function, f(Base&).
-
- ==============================================================================
-
- [14.4] What does it mean that "friendship is neither inherited nor transitive"?
-
- I may declare you as my friend, but that doesn't mean I necessarily trust
- either your kids or your friends.
- * I don't necessarily trust the kids of my friends. The privileges of
- friendship aren't inherited. Derived classes of a friend aren't necessarily
- friends. If class Fred declares that class Base is a friend, classes
- derived from Base don't have any automatic special access rights to Fred
- objects.
- * I don't necessarily trust the friends of my friends. The privileges of
- friendship aren't transitive. A friend of a friend isn't necessarily a
- friend. If class Fred declares class Wilma as a friend, and class Wilma
- declares class Betty as a friend, class Betty doesn't necessarily have any
- special access rights to Fred objects.
-
- ==============================================================================
-
- [14.5] Should my class declare a member function or a friend function?
-
- Use a member when you can, and a friend when you have to.
-
- Sometimes friends are syntactically better (e.g., in class Fred, friend
- functions allow the Fred parameter to be second, while members require it to be
- first). Another good use of friend functions are the binary infix arithmetic
- operators. E.g., aComplex + aComplex should be defined as a friend rather than
- a member if you want to allow aFloat + aComplex as well (member functions don't
- allow promotion of the left hand argument, since that would change the class of
- the object that is the recipient of the member function invocation).
-
- In other cases, choose a member function over a friend function.
-
- ==============================================================================
-
- SECTION [15]: Input/output via <iostream.h> and <stdio.h>
-
-
- [15.1] Why should I use <iostream.h> instead of the traditional <stdio.h>?
-
- Increase type safety, reduce errors, improve performance, allow extensibility,
- and provide subclassability.
-
- printf() is arguably not broken, and scanf() is perhaps livable despite being
- error prone, however both are limited with respect to what C++ I/O can do. C++
- I/O (using << and >>) is, relative to C (using printf() and scanf()):
- * Better type safety: With <iostream.h>, the type of object being I/O'd is
- known statically by the compiler. In contrast, <stdio.h> uses "%" fields to
- figure out the types dynamically.
- * Less error prone: With <iostream.h>, there are no redundant "%" tokens that
- have to be consistent with the actual objects being I/O'd. Removing
- redundancy removes a class of errors.
- * Extensible: The C++ <iostream.h> mechanism allows new user-defined types to
- be I/O'd without breaking existing code. Imagine the chaos if everyone was
- simultaneously adding new incompatible "%" fields to printf() and
- scanf()?!).
- * Subclassable: The C++ <iostream.h> mechanism is built from real classes such
- as ostream and istream. Unlike <stdio.h>'s FILE*, these are real classes
- and hence subclassable. This means you can have other user-defined things
- that look and act like streams, yet that do whatever strange and wonderful
- things you want. You automatically get to use the zillions of lines of I/O
- code written by users you don't even know, and they don't need to know about
- your "extended stream" class.
-
- ==============================================================================
-
- [15.2] Why does my program go into an infinite loop when someone enters an
- invalid input character?
-
- For example, suppose you have the following code that reads integers from cin:
-
- #include <iostream.h>
-
- int main()
- {
- cout << "Enter numbers separated by whitespace (use -1 to quit): ";
- int i = 0;
- while (i != -1) {
- cin >> i; // BAD FORM -- See comments below
- cout << "You entered " << i << '\n';
- }
- }
-
- The problem with this code is that it lacks any checking to see if someone
- entered an invalid input character. In particular, if someone enters something
- that doesn't look like an integer (such as an 'x'), the stream cin goes into a
- "failed state," and all subsequent input attempts return immediately without
- doing anything. In other words, the program enters an infinite loop; if 42 was
- the last number that was successfully read, the program will print the message
- You entered 42 over and over.
-
- An easy way to check for invalid input is to move the input request from the
- body of the while loop into the control-expression of the while loop. E.g.,
-
- #include <iostream.h>
-
- int main()
- {
- cout << "Enter a number, or -1 to quit: ";
- int i = 0;
- while (cin >> i) { // GOOD FORM
- if (i == -1) break;
- cout << "You entered " << i << '\n';
- }
- }
-
- This will cause the while loop to exit either when you hit end-of-file, or when
- you enter a bad integer, or when you enter -1.
-
- (Naturally you can eliminate the break by changing the while loop expression
- from while (cin >> i) to while ((cin >> i) && (i != -1)), but that's not really
- the point of this FAQ since this FAQ has to do with iostreams rather than
- generic structured programming guidelines.)
-
- ==============================================================================
-
- [15.3] How does that funky while (cin >> foo) syntax work?
-
- See the previous FAQ[15.2] for an example of the "funky while (cin >> foo)
- syntax."
-
- The expression (cin >> foo) calls the appropriate operator>> (for example, it
- calls the operator>> that takes an istream on the left and, if foo is of type
- int, an int& on the right). The istream operator>> functions return their left
- argument by convention, which in this case means it will return cin. Next the
- compiler notices that the returned istream is in a boolean context, so it
- converts that istream into a boolean.
-
- To convert an istream into a boolean, the compiler calls a member function
- called istream::operator void*(). This returns a void* pointer, which is in
- turn converted to a boolean (NULL becomes false, any other pointer becomes
- true). So in this case the compiler generates a call to cin.operator void*(),
- just as if you had casted it explicitly such as (void*)cin.
-
- The operator void*() cast operator returns some non-NULL pointer if the stream
- is in a good state, or NULL if it's in a failed state. For example, if you
- read one too many times (e.g., if you're already at end-of-file), or if the
- actual info on the input stream isn't valid for the type of foo (e.g., if foo
- is an int and the data is an 'x' character), the stream will go into a failed
- state and the cast operator will return NULL.
-
- The reason operator>> doesn't simply return a bool (or void*) indicating
- whether it succeeded or failed is to support the "cascading" syntax:
-
- cin >> foo >> bar;
-
- The operator>> is left-associative, which means the above is parsed as:
-
- (cin >> foo) >> bar;
-
- In other words, if we replace operator>> with a normal function name such as
- readFrom(), this becomes the expression:
-
- readFrom( readFrom(cin, foo), bar);
-
- As always, we begin evaluating at the innermost expression. Because of the
- left-associativity of operator>>, this happens to be the left-most expression,
- cin >> foo. This expression returns cin (more precisely, it returns a
- reference to its left-hand argument) to the next expression. The next
- expression also returns (a reference to) cin, but this second reference is
- ignored since it's the outermost expression in this "expression statement."
-
- ==============================================================================
-
- [15.4] Why does my input seem to process past the end of file?
-
- Because the eof state may not get set until after a read is attempted past the
- end of file. That is, reading the last byte from a file might not set the eof
- state. E.g., suppose the input stream is mapped to a keyboard -- in that case
- it's not even theoretically possible for the C++ library to predict whether or
- not the character that the user just typed will be the last character.
-
- For example, the following code might have an off-by-one error with the count
- i:
-
- int i = 0;
- while (! cin.eof()) { // WRONG! (not reliable)
- cin >> x;
- ++i;
- // Work with x ...
- }
-
- What you really need is:
-
- int i = 0;
- while (cin >> x) { // RIGHT! (reliable)
- ++i;
- // Work with x ...
- }
-
- ==============================================================================
-
- [15.5] Why is my program ignoring my input request after the first iteration?
-
- Because the numerical extractor leaves non-digits behind in the input buffer.
-
- If your code looks like this:
-
- char name[1000];
- int age;
-
- for (;;) {
- cout << "Name: ";
- cin >> name;
- cout << "Age: ";
- cin >> age;
- }
-
- What you really want is:
-
- for (;;) {
- cout << "Name: ";
- cin >> name;
- cout << "Age: ";
- cin >> age;
- cin.ignore(INT_MAX, '\n');
- }
-
- ==============================================================================
-
- [15.6] How can I provide printing for my class Fred?
-
- Use operator overloading[13] to provide a friend[14] left-shift operator,
- operator<<.
-
- #include <iostream.h>
-
- class Fred {
- public:
- friend ostream& operator<< (ostream& o, const Fred& fred);
- // ...
- private:
- int i_; // Just for illustration
- };
-
- ostream& operator<< (ostream& o, const Fred& fred)
- {
- return o << fred.i_;
- }
-
- int main()
- {
- Fred f;
- cout << "My Fred object: " << f << "\n";
- }
-
- We use a non-member function (a friend[14] in this case) since the Fred object
- is the right-hand operand of the << operator. If the Fred object was supposed
- to be on the left hand side of the << (that is, myFred << cout rather than
- cout << myFred), we could have used a member function named operator<<.
-
- Note that operator<< returns the stream. This is so the output operations can
- be cascaded[15.3].
-
- ==============================================================================
-
- [15.7] How can I provide input for my class Fred?
-
- Use operator overloading[13] to provide a friend[14] right-shift operator,
- operator>>. This is similar to the output operator[15.6], except the parameter
- doesn't have a const[18]: "Fred&" rather than "const Fred&".
-
- #include <iostream.h>
-
- class Fred {
- public:
- friend istream& operator>> (istream& i, Fred& fred);
- // ...
- private:
- int i_; // Just for illustration
- };
-
- istream& operator>> (istream& i, Fred& fred)
- {
- return i >> fred.i_;
- }
-
- int main()
- {
- Fred f;
- cout << "Enter a Fred object: ";
- cin >> f;
- // ...
- }
-
- Note that operator>> returns the stream. This is so the input operations can
- be cascaded and/or used in a while loop or if statement[15.3].
-
- ==============================================================================
-
- [15.8] How can I provide printing for an entire hierarchy of classes?
-
- Provide a friend[14] operator<<[15.6] that calls a protected virtual[20]
- function:
-
- class Base {
- public:
- friend ostream& operator<< (ostream& o, const Base& b);
- // ...
- protected:
- virtual void print(ostream& o) const;
- };
-
- inline ostream& operator<< (ostream& o, const Base& b)
- {
- b.print(o);
- return o;
- }
-
- class Derived : public Base {
- protected:
- virtual void print(ostream& o) const;
- };
-
- The end result is that operator<< acts as if it was dynamically bound, even
- though it's a friend[14] function. This is called the Virtual Friend Function
- Idiom.
-
- Note that derived classes override print(ostream&) const. In particular, they
- do not provide their own operator<<.
-
- Naturally if Base is an ABC[22.3], Base::print(ostream&) const can be declared
- pure virtual[22.4] using the "= 0" syntax.
-
- ==============================================================================
-
- [15.9] How can I "reopen" cin and cout in binary mode under DOS and/or OS/2?
-
- This is implementation dependent. Check with your compiler's documentation.
-
- For example, suppose you want to do binary I/O using cin and cout. Suppose
- further that your operating system (such as DOS or OS/2) insists on translating
- "\r\n" into "\n" on input from cin, and "\n" to "\r\n" on output to cout or
- cerr.
-
- Unfortunately there is no standard way to cause cin, cout, and/or cerr to be
- opened in binary mode. Closing the streams and attempting to reopen them in
- binary mode might have unexpected or undesirable results.
-
- On systems where it makes a difference, the implementation might provide a way
- to make them binary streams, but you would have to check the manuals to find
- out.
-
- ==============================================================================
-
- [15.10] Why can't I open a file in a different directory such as "..\test.dat"?
- [UPDATED!]
-
- [Recently added an explanation that the library routines treat "/" and "\"
- interchangeably thanks to Stan Brown (on 1/00).]
-
- Because "\t" is a tab character.
-
- You should use forward slashes in your filenames, even on an operating system
- that uses backslashes such as DOS, Windows, OS/2, etc. For example:
-
- #include <iostream.h>
- #include <fstream.h>
-
- int main()
- {
- #if 1
- ifstsream file("../test.dat"); // RIGHT!
- #else
- ifstsream file("..\test.dat"); // WRONG!
- #endif
-
- // ...
- }
-
- Remember, the backslash ("\") is used in string literals to create special
- characters: "\n" is a newline, "\b" is a backspace, and "\t" is a tab, "\a" is
- an "alert", "\v" is a vertical-tab, etc. Therefore the file name
- "\version\next\alpha\beta\test.dat" is interpreted as a bunch of very funny
- characters; use "/version/next/alpha/beta/test.dat" instead, even on systems
- that use a "\" as the directory separator such as DOS, Windows, OS/2, etc.
- This is because the library routines on these operating systems handle "/" and
- "\" interchangeably.
-
- ==============================================================================
-
- --
- Marshall Cline / 972-931-9470 / mailto:cline@parashift.com
-