📄 ec5.htm
字号:
The reason for making a member private or protected is to limit access to it, right? Your overworked, underpaid C++ compilers go to lots of trouble to make sure that your access restrictions aren't circumvented, right? So it doesn't make a lot of sense for you to write functions that give random clients the ability to freely access restricted members, now, does it? If you think it does make sense, please reread this paragraph over and over until you agree that it doesn't.It's easy to violate this simple rule. Here's an example: class Address { ... }; // where someone livesclass Person {public: Address& personAddress() { return address; } ...private: Address address; ...};The member function personAddress provides the caller with the Address object contained in the Person object, but, probably due to efficiency considerations, the result is returned by reference instead of by value (see Item 22). Unfortunately, the presence of this member function defeats the purpose of making Person::address private: Person scott(...); // parameters omitted for // simplicityAddress& addr = // assume that addr is scott.personAddress(); // globalNow the global object addr is another name for scott.address, and it can be used to read and write scott.address at will. For all practical purposes, scott.address is no longer private; it is public, and the source of this promotion in accessibility is the member function personAddress. Of course, there is nothing special about the access level private in this example; if address were protected, exactly the same reasoning would apply.References aren't the only cause for concern. Pointers can play this game, too. Here's the same example, but using pointers this time: class Person {public: Address * personAddress() { return &address; } ...private: Address address; ...};Address *addrPtr = scott.personAddress(); // same problem as aboveWith pointers, however, you have to worry not only about data members, but also about member functions. That's because it's possible to return a pointer to a member function: class Person; // forward declaration// PPMF = "pointer to Person member function"typedef void (Person::*PPMF)();class Person {public: static PPMF verificationFunction() { return &Person::verifyAddress; } ...private: Address address; void verifyAddress();};If you're not used to socializing with pointers to member functions and typedefs thereof, the declaration for Person::verificationFunction may seem daunting. Don't be intimidated. All it says is verificationFunction is a member function that takes no parameters; its return value is a pointer to a member function of the Person class; the pointed-to function (i.e., verificationFunction's return value) takes no parameters and returns nothing, i.e., void.As for the word static, that means what it always means in a member declaration: there is only one copy of the member for the entire class, and the member can be accessed without an object. For the complete story, consult your favorite introductory C++ textbook. (If your favorite introductory C++ textbook doesn't discuss static members, carefully tear out all its pages and recycle them. Dispose of the book's cover in an environmentally sound manner, then borrow or buy a better textbook.)In this last example, verifyAddress is a private member function, indicating that it's really an implementation detail of the class; only class members should know about it (and friends, too, of course). However, the public member function verificationFunction returns a pointer to verifyAddress, so clients can again pull this kind of thing: PPMF pmf = scott.verificationFunction();(scott.*pmf)(); // same as calling // scott.verifyAddressHere, pmf has become a synonym for Person::verifyAddress, with the crucial difference that there are no restrictions on its use.In spite of the foregoing discussion, you may someday be faced with a situation in which, pressed to achieve performance constraints, you honestly need to write a member function that returns a reference or a pointer to a less-accessible member. At the same time, however, you won't want to sacrifice the access restrictions that private and protected afford you. In those cases, you can almost always achieve both goals by returning a pointer or a reference to a const object. For details, take a look at Item 21. Back to Item 30: Avoid member functions that return non-const pointers or references to members less accessible than themselves.Continue to Item 32: Postpone variable definitions as long as possible.Item 31: Never return a reference to a local object or to a dereferenced pointer initialized by new within the function.This Item may sound complicated, but it's not. It's simple common sense. Really. Honest. Trust me.Consider first the matter of returning a reference to a local object. The problem here is that local objects are just that, local. That means they're constructed when they're defined, and they're destructed when they go out of scope. Their scope, however, is that of the function body in which they're located. When the function returns, control leaves its scope, so the objects local to that function are automatically destructed. As a result, if you return a reference to a local object, that local object has been destructed before the caller of the function ever gets its computational hands on it.This problem usually raises its ugly head when you try to improve the efficiency of a function by returning its result by reference instead of by value. The following example is the same as the one in Item 23, which pursues in detail the question of when you can return a reference and when you can't: class Rational { // class for rational numberspublic: Rational(int numerator = 0, int denominator = 1); ~Rational(); ...private: int n, d; // numerator and denominator// notice that operator* (incorrectly) returns a referencefriend const Rational& operator*(const Rational& lhs, const Rational& rhs);};// an incorrect implementation of operator*inline const Rational& operator*(const Rational& lhs, const Rational& rhs){ Rational result(lhs.n * rhs.n, lhs.d * rhs.d); return result;}Here, the local object result is constructed upon entry into the body of operator*. However, local objects are automatically destroyed when they go out of scope. result will go out of scope after execution of the return statement, so when you write this, Rational two = 2;Rational four = two * two; // same as // operator*(two, two)what happens during the function call is this: The local object result is constructed. A reference is initialized to be another name for result, and this reference is squirreled away as operator*'s return value. The local object result is destroyed, and the space it used to occupy on the stack is made available for use by other parts of the program or by other programs. The object four is initialized using the reference of step 2.Everything is fine until step 4, at which point there occurs, as they say in the highest of high-tech circles, "a major lossage." The reference initialized in step 2 ceased to refer to a valid object as of the end of step 3, so the outcome of the initialization of object four is completely undefined.The lesson should be clear: don't return a reference to a local object."Okay," you say, "the problem is that the object I want to use goes out of scope too soon. I can fix that. I'll just call new instead of using a local object." Like this: // another incorrect implementation of operator*inline const Rational& operator*(const Rational& lhs, const Rational& rhs){ // create a new object on the heap Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d); // return it return *result;}This approach does indeed avoid the problem of the previous example, but it introduces a new one in its place. To avoid a memory leak in your software, you know you must ensure that delete is applied to every pointer conjured up by new, but ay, there's the rub: who's to make the matching call to delete for this function's use of new?Clearly, the caller of operator* must see to it that delete is applied. Clear, yes, and even easy to document, but nonetheless the cause is hopeless. There are two reasons for this pessimistic assessment.First, it's well-known that programmers, as a breed, are sloppy. That doesn't mean that you're sloppy or that I'm sloppy, but rare is the programmer who doesn't work with someone who is shall we say? a little on the flaky side. What are the odds that such programmers and we all know that they exist will remember that whenever they call operator*, they must take the address of the result and then use delete on it? That is, they must use operator* like this: const Rational& four = two * two; // get dereferenced // pointer; store it in // a reference...delete &four; // retrieve pointer // and delete itThe odds are vanishingly small. Remember, if only a single caller of operator* fails to follow the rules, you have a memory leak.Returning dereferenced pointers has a second, more serious, problem, because it persists even in the presence of the most conscientious of programmers. Often, the result of operator* is a temporary intermediate value, an object that exists only for the purposes of evaluating a larger expression. For example: Rational one(1), two(2), three(3), four(4);Rational product;product = one * two * three * four;Evaluation of the expression to be assigned to product requires three separate calls to operator*, a fact that becomes more evident when you rewrite the expression in its equivalent functional form: product = operator*(operator*(operator*(one, two), three), four);You know that each of the calls to operator* returns an object that needs to be deleted, but there is no possibility of applying delete, because none of the returned objects has been saved anywhere.The only solution to this difficulty is to ask clients to code like this: const Rational& temp1 = one * two;const Rational& temp2 = temp1 * three;const Rational& temp3 = temp2 * four;delete &temp1;delete &temp2;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -