Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 66
Текст из файла (страница 66)
It indicates invariance to other programmers and ensures thatthe compiler enforces it.20.5Choosing Class, Method and Parameter NamesThe title of this chapter includes the word ”comprehensible”, and this isan important issue when it comes to naming your class and the methodsof its API. In all cases, you should strive for clear and distinct names,without making them so long as to be burdensome or, worse, abbreviatingeach to an acronym of your own choosing. Likewise, the names of theparameters in each method should be clear and descriptive. If you can,make it possible for your clients to write comprehensible code too.
Forexample, in methods that take a number of initialization values, it’s agood idea to use custom enumerations with clear names. Not only doesthis make it easy for your client to work out which settings to choose, butCHOOSING CLASS, METHOD AND PARAMETER NAMES313it’s easier when looking back at the code to understand what it means. Forexample, take the following function declaration in class CGpSurgery:class CGpSurgery : public CBase{public:void MakeHospitalAppointment(TBool aExistingPatient,TBool aUrgency, TBool aTestData);... // Other functions omitted for clarity};Without looking at the declaration of MakeHospitalAppointment(), it’s not immediately clear how to call the method, for exampleto make an urgent hospital appointment for a new patient who alreadyhas some test data available. Unless you name your parameters carefully,your client may well have to consult your documentation too, to find outthe appropriate boolean value for each variable.MakeHospitalAppointment(EFalse, ETrue, ETrue);A far clearer approach is to use enumeration values whose nameclearly indicates their purpose.
However, if you define them in globalscope, you or your clients may well find that a name clash arises withcode in other header files, particularly if you choose short, simple namessuch as TSize or TColor. Even if your code doesn’t get a clash, this(ab)use of the global scope means that code which includes your headermay get a conflict later down the line. It’s preferable to define them insidethe class to which they apply. The caller then uses the class scope toidentify them. This is a useful approach for class-specific enumerations,typedefs and constants. If you prefer to keep them out of your class scope,you could alternatively use a C++ namespace to prevent spilling yourdefinitions into the global scope.class CGpSurgery : public CBase{public:enum TPatientStatus { EExistingPatient, ENewPatient };enum TAppointmentUrgency { EUrgent, ERoutine };enum TTestData { ETestResultsPending, ETestResultsAvailable };public:void MakeHospitalAppointment(TPatientStatus aExistingRecords,TAppointmentUrgency aUrgency, TTestData aTestData);};Looking at the function call, it is now significantly clearer what eachparameter refers to; in effect, the code has documented itself.MakeHospitalAppointment(CGpSurgery::ENewPatient, CGpSurgery::EUrgent,CGpSurgery::ETestResultsAvailable);314EXPOSE A COMPREHENSIVE AND COMPREHENSIBLE APIThe use of enumerations rather than boolean values also providesextensibility in the future; in the case of the CGpSurgery class, additional levels of appointment urgency, patient status or test data availabilitycan be introduced without the need to change the signature of MakeHospitalAppointment().
This avoids breaking compatibility – which isthe subject of Chapter 18.20.6Compiler-Generated FunctionsI’ve discussed some of the things to consider when defining your class, butbefore concluding this chapter, it’s worth briefly describing the functionsthat the compiler generates for you if you don’t add them yourself. If youhave not declared a copy constructor, assignment operator or destructor,the compiler generates them implicitly for a class in case they need tobe invoked. If there are no constructors declared, it declares a defaultconstructor too.The implicitly-generated functions are public; the constructor anddestructor are simply placeholders for the compiler to add the coderequired to create and destroy an object of the class (for example, toset up or destroy the virtual function table).
The destructor does notperform any cleanup code. The compiler-generated copy constructor andassignment operator perform a copy or assignment on each memberof your class, invoking the copy constructor or assignment operatorif there is one defined, or applying the rule for the members of theencapsulated object if not. Built-in types, pointers and references arecopied or assigned using a shallow bitwise copy. This is problematic forpointers to objects on the heap, since a bitwise copy is rarely desirable,opening up opportunities for dangling pointers; these can result in amemory leak or multiple deletion through the same pointer, which raisesa panic (USER 44).This is particularly true for CBase-derived classes which should beconstructed through the NewL() and NewLC() functions, which areguaranteed to initialize the object correctly using two-phase construction(as described in Chapter 4).
They should not be copy constructed orassigned to, because this bypasses the two-phase construction and makesshallow copies of any pointers to dynamic memory.To prevent accidental copies, CBase declares a private copy constructor and assignment operator (as you’ll see from the class definition ine32base.h). Declaring, but not defining, a private copy constructor andassignment operator prevents calling code from performing invalid copyoperations using compiler-generated code. If your C class does need acopy constructor, you must explicitly declare and define one publicly – orprovide a CloneL() or CopyL() method, which allows you to makeleaving calls, which are not possible in a copy constructor.SUMMARY315In fact, you’ll probably not find yourself implementing a copy constructor or assignment operator on Symbian OS very often. As I’ve explained,C class objects are not suitable for copy or assignment, while T classobjects are ideal candidates for allowing the compiler to generate its ownimplicit versions.
An R class object is unlikely to be copied and is safe forbitwise copy anyway. You would usually not expect to define an explicitcopy constructor or assignment operator for an R class unless a shallowcopy of the resource handle causes problems. For example, while thefirst object to call Close() releases the underlying resource and zeroesthe handle value (making it invalid), a second object may still have anon-zero handle value.
If the second object attempts to use the handle,even just to call Close(), the behavior is undefined and depends on thenature of the resource.You can prevent a client copying objects of your R class by declaringthe copy constructor and assignment operator private and not implementing them. If taking a copy is a valid action, you should declareand define the copy constructor and assignment operator, or provideanother method, such as CloneL(), by which the resource handle iscopied safely.If your class does not need a destructor, for example because it hasno cleanup code, you are under no obligation to add one.
A goodexample of this is a T or R class, described in more detail in Chapter 1;neither type of class has need of an explicit destructor. For C classes,any compiler-generated destructor is virtual by default since the parentclass, CBase, defines a virtual destructor. This means that, if your C classinherits from a class which defines a destructor, it is called correctly,regardless of whether you declare and define an empty destructor orallow the compiler to do it for you.20.7 SummaryThis chapter stressed the importance of defining classes clearly and comprehensively so that code is re-used rather than re-written or duplicated.It discussed a number of issues, most of which are not just specific toSymbian OS, but relate generally to C++ best practice, including:• a comparison of the relative merits of passing and returning by value,reference or pointer• the use of const where appropriate• functional abstraction and how (not) to expose member data• the use of enumerations to clarify the role of a function parameter andallow it to be extended in future316EXPOSE A COMPREHENSIVE AND COMPREHENSIBLE API• the functions a compiler will generate for a class if they are notdeclared.This chapter also discussed the use of IMPORT_C and EXPORT_C onSymbian OS to export API functions for use by external client code.
Thechoice of when to export a function is relatively straightforward:• Virtual functions should always be exported unless the class is nonderivable or where the functions are pure virtual (because there is nocode to export, except in rare cases).• Code which exports virtual functions must also export a constructor,even if it is empty, in order for the virtual function table to be createdcorrectly. If necessary, the constructor should be specified as protectedto prevent it being called directly by clients of the class.• Inline functions should never be exported.• Only functions that are used outside the DLL (called either directly orindirectly through a call from an inline function) should be exported.21Good Code StyleWe all learn from our mistakes. The trick is to learnfrom the mistakes of othersAnonSymbian OS is designed to work well on devices with limited resourceswhile still providing rich functionality.
To balance the demand on systemresources with their relative scarcity requires program code to be of highquality; in particular, robust, efficient and compact.You can find advice on writing good-quality C++ code in any decenttextbook on the subject. There are entire books which cover aspects ofgood programming style and there isn’t enough space in this book to gointo too much detail.