Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 65
Текст из файла (страница 65)
In general, if an object doesn’t exist when the function thatreturns it is called, you can only return it by reference if some otherobject, whose life-time extends beyond the end of that function,has ownership of it.Pass and Return by const Pointervoid PassByConstPtr(const CExample* aParameter);const CExample* ReturnAConstPtr();Passing or returning a constant pointer is similar to using a constantreference in that no copy is taken and the recipient cannot change theobject.2 It’s useful to return a pointer when returning access to a C++array, or where an uninitialized parameter or return value is meaningful.There’s no such thing as an uninitialized reference, so returning a NULLpointer is the only valid way of indicating ”no object” in a return value.However, you might consider overloading a method to take an additionalconstant reference input parameter.
This means that you can use a constreference for cases where the input parameter can be supplied, and callthe overloaded method which doesn’t take any input parameter when noinput is supplied. For example:// Take a const pointer (aInput may be NULL)void StartL(const CClanger* aInput, const TDesC8& aSettings);// Or alternatively overload to take a const reference when it is valid// Use this overload in cases where aInput is NULLvoid StartL(const TDesC8& aSettings);// Use this overload in cases where aInput!=NULLvoid StartL(const CClanger& aInput, const TDesC8& aSettings);The benefit of using a reference over a pointer is that it is alwaysguaranteed to refer to an object.
In the first overload, StartL() wouldhave to implement a check to see whether aInput points to an objectbefore dereferencing it, in case it is NULL. This is unnecessary in the thirdoverload, where aInput is guaranteed to refer to an object.2The constness of the pointer can, of course, be cast away using const_cast<CExample*>(aParameter), but you wouldn’t do that without good reason, wouldyou?308EXPOSE A COMPREHENSIVE AND COMPREHENSIBLE APIPass and Return by Pointervoid PassByPtr(CExample* aParameter);CExample* ReturnAPtr();Passing or returning a pointer allows the contents to be modified by therecipient. As with const pointers, you should pass or return a pointerif it can be NULL, if it points into a C++ array or if you are transferringownership of an object to a caller.
On Symbian OS, passing or returninga pointer often indicates a transfer of ownership and it’s preferable notto return a pointer if you are not transferring ownership (since you mustthen document clearly to callers that they should not delete it whenthey’ve finished with it). In fact, you should prefer to pass by referenceor return a reference rather than use pointers except for the reasonsdescribed.All else being equal, a reference can be more efficient than a pointerwhen the compiler must maintain a NULL pointer through a conversion.Consider the following class and pseudo-code:class CSoupDragon : public CBase, public MDragon{...};CSoupDragon* soupDragon;MDragon* dragon;...dragon = soupDragon; // soupDragon may be NULLFor the conversion between CSoupDragon and MDragon, the compiler must add sizeof(CBase) to the soupDragon pointer in all cases,except where soupDragon is NULL, whereupon it must continue to beNULL rather than point incorrectly at the address which is the equivalentof sizeof(CBase).
Thus the compiler must effect the following:dragon = (MDragon*)(soupDragon ? (TUint8*)soupDragon+sizeof(CBase) :NULL);For a conversion which involves references rather than pointers, thetest is unnecessary, since a reference must always refer to an object.For any of your code that receives a pointer return value, or if youimplement methods that take pointer parameters, you should considerthe implications of receiving a NULL pointer. If an uninitialized value isincorrect, i.e. a programming error, you should use an assertion statementto verify that it is valid.
The use of assertions for ”defensive” programmingis discussed further in Chapter 16.MEMBER DATA AND FUNCTIONAL ABSTRACTION30920.4 Member Data and Functional AbstractionIn this discussion on the API of your class, I’ve discussed the definition ofthe member functions of your class, but not really touched on the memberdata.
There’s a very simple rule, which is that you should not make classdata public, and there’s a good reason for this – encapsulation.The benefit of keeping your member data private to the class is thatyou can control access to it. First, you can decide whether to expose thedata at all; if you choose to do so, you can provide member functionsin your class to expose the data. If you follow the guidelines above, youcan control precisely the type of access allowed.
If you return constreferences, const pointers or a value, the caller has read-only access,while returning a reference or a pointer allows the caller to modify thedata, as illustrated by the following:const TExample& ReadOnlyReference();const TExample* ReadOnlyPointer();TExample ReadOnlyValue();TExample& ReadWriteReference();TExample* ReadWritePointer();An additional benefit of keeping class member data private is a degreeof functional abstraction. By providing methods to Set() and Get(),the variable is not exposed directly.
Should you later decide to changethe implementation of the class, you can do so without requiring yourclients to update their code.For example, consider this rather contrived class which stores a password and compares it with a password typed in later, providing are-usable class for applications to protect their user files. In version 1.0,the unencrypted password is, rather naively, stored within the object.The code is released and everyone is happy for a while, including a fewhackers. A code review before version 2.0 highlights the problem.
If theclass has been declared as follows, any attempt to add more security tothe class forces clients of the class to change their code. Incidentally, Idiscuss how to maintain compatibility for client code in Chapter 18.// Version 1.0class CPassword : public CBase{public:... // Other functions omitted for claritypublic:// Bad! There are no access functions – the member data is public// The caller can set and get the password directlyHBufC8* iPassword;};310EXPOSE A COMPREHENSIVE AND COMPREHENSIBLE APIThe same problem arises in a second definition (version 1.1) below,but for different reasons. This class is a step in the right direction, becausethe password data is at least private.
To set and get the password, thecaller must call a method explicitly rather than modify the contents of theobject directly.// Version 1.1class CPassword : public CBase{public:...IMPORT_C void SetPasswordL(const TDesC8& aPassword);inline HBufC8* Password(); // Better! But not greatprivate:HBufC8* iPassword; // Better. Password is private};inline HBufC8* CPassword::Password(){return (iPassword);}// SetPasswordL() is a leaving method because allocation// of iPassword may leave if there is insufficient memoryEXPORT_C void CPassword::SetPasswordL(const TDesC8& aPassword){HBufC8* newPassword = aPassword.AllocL();delete iPassword;iPassword = newPassword;}To access the data, the caller must now call Password(), whichprovides read/write access to iPassword.
Sure, it returns a constantheap descriptor, but from Chapters 5 and 6, you know how to call Des()on that pointer to get a modifiable pointer which can be used to updatethe contents of the buffer. This is far from ideal; the password shouldonly be modifiable through the SetPasswordL() method, otherwiseit’s confusing for the client to know which to use. To get read-onlyaccess, the method should either return a const HBufC8* or, preferably,return a constant reference to the more generic descriptor type, TDesC8,as follows:// Version 1.2class CPassword : public CBase{public:...// Better! Now a const method and return valueinline const TDesC8& Password() const;private:MEMBER DATA AND FUNCTIONAL ABSTRACTION311HBufC8* iPassword;};inline const TDes8C& CPassword::Password() const{return (*iPassword);}In fact, for access to the password, this class definition is not muchof an improvement on the original one, because the method exposesthe class implementation directly.
It’s questionable whether there’s muchbenefit over the original version, since it doesn’t do anything additional toversion 1.0, other than requiring a function call to retrieve the password.In fact, by implementing the accessor in an inline method, the additionaloverhead of a function call is removed at compile time.
This is notnecessarily a good thing in this case because its implementation iscompiled into the caller’s code. It isn’t possible to update Password()without forcing clients of the class to recompile to receive the benefit ofthe new implementation, as I discussed in Chapter 18.Here’s a better definition of the class (version 1.3). Notice that the datamember is private; there is a single method to set the password and, ratherthan returning the password, a method is provided to compare an inputpassword with the stored value, returning a TBool result to indicate amatch.
This method is not inlined:// Version 1.3class CPassword : public CBase{public:IMPORT_C TBool ComparePassword(const TDesC8& aInput) const;// Implemented as shown previouslyIMPORT_C void SetPasswordL(const TDesC8& aPassword);private:HBufC8* iPassword;};EXPORT_C TBool CPassword::ComparePassword(const TDesC8& aInput) const{return (0==iPassword->Compare(aInput));}A more secure storage mechanism is used in version 2.0, which doesn’trequire the definition of class CPassword to change, but updates theinternals of SetPasswordL() and ComparePassword() to store thepassword as a hash value, rather than ”in the clear”. By abstracting thefunctionality into the class, when version 2.0 is released existing clientsof the library are unaffected by the upgrade.312EXPOSE A COMPREHENSIVE AND COMPREHENSIBLE API// Version 2.0EXPORT_C void CPassword::SetPasswordL(const TDesC8& aPassword){TBuf8<KMaxHashSize> hashed;// Fill hashed with a hash of aPassword using an appropriate method// ...HBufC8* newPassword = hashed.AllocL();delete iPassword;iPassword = newPassword;}EXPORT_C TBool CPassword::ComparePassword(const TDesC8& aInput) const{TBuf8<KMaxHashSize> hashed;// Fill hashed with a hash of aInput using an appropriate method// ...return (0==iPassword->Compare(hashed));}Of course, there are still problems with this class, not least the factthat there is no error checking to see whether iPassword has actuallybeen set by a call to SetPasswordL().
A better implementation wouldperhaps remove SetPasswordL() and require the password to bepassed to a NewL() or NewLC() two-phase construction method, asdescribed in Chapter 4. This would also limit when, in the object’slifetime, the password could change, for better security. I’ll leave thatimplementation as an exercise for the reader.Where possible, use const on parameters, return values, ”query”methods, and other methods that do not modify the internals of aclass.