Friday, August 6, 2010

Cpp Theory Questions 12

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).

---------------------------------------------------------------------------------------------------------------------

 

Do friends violate encapsulation?

 

No! If they're used properly, they 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.

 

(Many people think of a friend function as something outside the class. Instead, try thinking of a friend function as part of the class's public interface. A friend function in the class declaration doesn't violate encapsulation any more than a public member function violates encapsulation: both have exactly the same authority with respect to accessing the class's non-public parts.)

---------------------------------------------------------------------------------------------------------------------

 

Why should I use <iostream> instead of the traditional <cstdio>?

 

Increase type safety, reduce errors, allow extensibility, and provide inheritability.

 

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()):

 

More type-safe: With , the type of object being I/O'd is known statically by the compiler. In contrast, uses "%" fields to figure out the types dynamically.

Less error prone: With , 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++ 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()?!

Inheritable: The C++ mechanism is built from real classes such as std::ostream and std::istream. Unlike 's FILE*, these are real classes and hence inheritable. 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.

---------------------------------------------------------------------------------------------------------------------

 

How does that funky while (std::cin >> foo) syntax work?

 

The expression (std::cin >> foo) calls the appropriate operator>> (for example, it calls the operator>> that takes an std::istream on the left and, if foo is of type int, an int& on the right). The std::istream operator>> functions return their left argument by convention, which in this case means it will return std::cin. Next the compiler notices that the returned std::istream is in a boolean context, so it converts that std::istream into a boolean.

 

To convert an std::istream into a boolean, the compiler calls a member function called std::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 std::cin.operator void*(), just as if you had casted it explicitly such as (void*) std::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:

 

 

std::cin >> foo >> bar;

The operator>> is left-associative, which means the above is parsed as:

 

 

(std::cin >> foo) >> bar;

In other words, if we replace operator>> with a normal function name such as readFrom(), this becomes the expression:

 

 

readFrom( readFrom(std::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, std::cin >> foo. This expression returns std::cin (more precisely, it returns a reference to its left-hand argument) to the next expression. The next expression also returns (a reference to) std::cin, but this second reference is ignored since it's the outermost expression in this "expression statement."

---------------------------------------------------------------------------------------------------------------------

 

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 std::cin:

 

 

#include

 

int main()

{

std::cout << "Enter numbers separated by whitespace (use -1 to quit): ";

int i = 0;

while (i != -1) {

std::cin >> i; // BAD FORM — See comments below

std::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 std::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

 

int main()

{

std::cout << "Enter a number, or -1 to quit: ";

int i = 0;

while (std::cin >> i) { // GOOD FORM

if (i == -1) break;

std::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 (std::cin >> i) to while ((std::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.)

---------------------------------------------------------------------------------------------------------------------

 

How can I provide printing for my class Fred?

 

Use operator overloading to provide a friend left-shift operator, operator<<.

 

 

#include

 

class Fred {

public:

friend std::ostream& operator<< (std::ostream& o, const Fred& fred);

...

private:

int i_; // Just for illustration

};

 

std::ostream& operator<< (std::ostream& o, const Fred& fred)

{

return o << fred.i_;

}

 

int main()

{

Fred f;

std::cout << "My Fred object: " << f << "\n";

...

}

We use a non-member function (a friend 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 << std::cout rather than std::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.

No comments:

Post a Comment