Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 64
Текст из файла (страница 64)
Admittedly, this is slightlyconfusing, until you consider that the client will be including the headerfile, so they are effectively ”importing” your function definition into theircode module. You should mark the corresponding function definitionin the .cpp file with EXPORT_C. The number of functions markedIMPORT_C in the header files must match the number marked EXPORT_Cin the source.Here’s an example of the use of the macros, which will doubtless lookfamiliar from class definitions in the header files supplied by whicheverSymbian OS SDK you use.class CMyExample : public CSomeBase{public:IMPORT_C static CMyExample* NewL();public:IMPORT_C void Foo();...};EXPORT_C CMyExample* CMyExample::NewL(){...}302EXPOSE A COMPREHENSIVE AND COMPREHENSIBLE APIEXPORT_C void CMyExample::Foo(){...}The rules as to which functions you need to export are quite simple.Firstly, you must never export an inline function.
There’s simply no needto do so! As I described, IMPORT_C and EXPORT_C add functions tothe export table to make them accessible to components linking againstthe library. However, the code of an inline function is, by definition,already accessible to the client, since it is declared within the header file.The compiler interprets the inline directive by adding the code directlyinto the client code wherever it calls it. In fact, if you export an inlinefunction, you force all DLLs which include the header file to export it too,because the compiler will generate an out-of-line copy of the functionin every object file which uses it. You’ll find a warning about the useof inline directives except for the most trivial of functions in Chapters 18and 21.Only functions which need to be used outside a DLL should beexported. When you use IMPORT_C and EXPORT_C on a function, itadds an entry to the export table.
If the function is private to the class andcan never be accessed by client code, exporting it merely adds it to theexport table unnecessarily.Private functions should only be exported if:• they are virtual (I’ll discuss when to export virtual functions shortly)• they are called by a public inline function (this is clear when youconsider that the body of the inline function will be added to clientcode, which means that, in effect, it makes a direct call to theprivate function).Similarly, a protected function should only be exported if:• it is called by an inline function, as described above• it is designed to be called by a derived class, which may be implemented in another DLL• it is virtual.All virtual functions, public, protected or private, should be exported,since they may be re-implemented by a derived class in another codemodule. Any class which has virtual functions must also export a constructor, even if it is empty.The one case where you should not export a virtual function is if itis pure virtual.
This is obvious when you consider that there is generallyno implementation code for a pure virtual function, so there is no codePARAMETERS AND RETURN VALUES303to export. As long as a deriving class has access to the virtual functiontable for your base class, it is aware of the pure virtual function and isforced to implement it. In the rare cases where a pure virtual function hasa function body, it must be exported.The virtual function table for a class is created and updated by theconstructor of the base class and any intermediate classes. If you don’texport a constructor, when a separate code module comes to inheritfrom your class, the derived constructor will be unable to access thedefault constructor of your class in order to generate the virtual functiontable.
This is why any class which has virtual functions must export aconstructor. Remember the rule that you must not export inline functions?Since you’re exporting the constructor, you must implement it in yoursource module, even if it’s empty, rather than inline it. A good example ofthis is class CBase (defined in e32base.h), which defines and exportsa protected default constructor.The rules of exporting functions are as follows:• never export an inline function• only those non-virtual functions which need to be used outsidea DLL should be exported• private functions should only be exported if they are called bya public inline function• protected functions should be exported if they are called bya public inline function or if they are likely to be calledby a derived class which may be implemented in anothercode module• all virtual functions, public, protected or private, should be exported, since they may be re-implemented in a separate module• pure virtual functions should not be exported unless they contain code• any class which has virtual functions must also export a (noninlined) constructor, even if it is empty.20.3 Parameters and Return ValuesLet’s move on to think about the definitions of class methods in terms ofparameters and return values.
I’ll compare passing and returning values304EXPOSE A COMPREHENSIVE AND COMPREHENSIBLE APIby value, reference and pointer, and state a few general guidelines forgood C++ practice on Symbian OS.Pass and Return by Valuevoid PassByValue(TExample aParameter);TExample ReturnAValue();Passing a parameter by value, or returning a value, takes a copy ofthe argument or final return value.
This can potentially be expensive,since it invokes the copy constructor for the object (and any objectsit encapsulates) as well as using additional stack space for the copy.When the object passed in or returned goes out of scope, its destructor iscalled. This is certainly less efficient than passing a reference or pointerto the object as it currently exists, and you should avoid it for largeor complex objects such as descriptors.1 However, it is insignificant forsmall objects (say less than eight bytes) and the built-in types (TBool,TText, TInt, TUint and TReal). Of course, by taking a copy of theparameter, the original is left unchanged, so a parameter passed by valueis most definitely a constant input-only parameter.You may even choose to return an object by const value in somecases. This prevents cases of assignment to the return value – which isillegal for methods that return the built-in types.
You should strive tomake your own types behave like built-in types and may choose to use aconst return value to enforce this behavior when you are returning aninstance of your class by value.Pass and Return by const Referencevoid PassByConstRef(const TExample& aParameter);const TExample& ReturnAConstRef();Passing an object by const reference prevents the parameter from beingmodified and should be used for constant input parameters larger thanthe built-in types. Equally, for efficiency reasons, returning a value as aconstant reference should be used in preference to taking a copy of alarger object to return it by value. You must be careful when returningan object by reference, whether it is modifiable or constant, becausethe caller of the function and the function itself must agree on the1When passing objects by reference or pointer, a 32-bit pointer value is transferred.
Thisminimal memory requirement is fixed, regardless of the type to which it points. Internally, areference is implemented as a pointer, with additional syntax to remove the inconvenienceof indirection.PARAMETERS AND RETURN VALUES305lifetime of the object which is referenced. I’ll discuss this further in thenext section.Pass and Return by Referencevoid PassByRef(TExample& aParameter);TExample& ReturnARef();Passing an object by reference allows it to be modified; it is, in effect,an output or input/output parameter.
This is useful for both larger objectsand the built-in types, and is a common alternative to returning an objectby value.Returning a reference is commonly seen to allow read/write access, forexample to a heap-based object for which ownership is not transferred.Returning a reference passes access to an existing object to the caller;of course, the lifetime of this object must extend beyond the scope ofthe function.Consider the following example:class TColor{public:TColor(TInt aRed, TInt aGreen, TInt aBlue);TColor& AddColor(TInt aRed, TInt aGreen, TInt aBlue);//...
functions omitted for clarityprivate:TInt iRed;TInt iGreen;TInt iBlue;};TColor::TColor(TInt aRed, TInt aGreen, TInt aBlue): iRed(aRed), iGreen(aGreen), iBlue(aBlue){}TColor& TColor::AddColor(TInt aRed, TInt aGreen, TInt aBlue){// Incorrect implementation leads to undefined behaviorTColor newColor(aRed+iRed, iGreen+aGreen, iBlue+aBlue);return (newColor); // Whoops, new color goes out of scope}TColor prettyPink(250, 180, 250);TColor differentColor = prettyPink.AddColor(5, 10, 5);The AddColor() method returns a reference to newColor, a localstack-based object which ceases to exist outside the scope of AddColor().
The result is undefined, and some compilers flag this as anerror. If your compiler does let you build and run the code, it may evensucceed, since different compilers allow temporary variables different lifespans. In circumstances like these, that’s what ”undefined” means. What306EXPOSE A COMPREHENSIVE AND COMPREHENSIBLE APIworks with one compiler will doubtless fail with another and you shouldavoid any reliance upon compiler-specific behavior.The previous code is a classic example of knowing that it’s generallypreferable to return objects larger than the built-in types by referencerather than by value, but applying the rule where it is not actually valid todo so.
An equally invalid solution would be to create the return value onthe heap instead of the stack, to avoid it being de-scoped, and then returna reference to the heap object. For example, here’s another incorrectimplementation of adding two colors, this time in a leaving function.TColor& TColor::AddColorL(TInt aRed, TInt aGreen, TInt aBlue){// Incorrect implementation leads to a memory leakTColor* newColor = new (ELeave) TColor(aRed+iRed, iGreen+aGreen,iBlue+aBlue);return (*newColor);}The newColor object does at least exist, but who is going to deleteit? By returning a reference, the method gives no indication that theownership of newColor is being transferred to the method’s caller.Without clear documentation, the caller wouldn’t know that they’resupposed to delete the return value of AddColorL() when they’vefinished with it.TColor prettyPink(250, 180, 250);TColor differentColor = prettyPink.AddColor(5, 10, 5);...delete &differentColor; // Nasty.
It’s unusual to delete a reference!This just isn’t the done thing with functions which return objects byreference! Even if most of your callers read the documentation and don’tobject heartily to this kind of behavior, it only takes one less observantcaller to leak memory.Of course, there is an expense incurred in return by value, namely thecopy constructor of the object returned and, later, its destructor. But insome cases, such as that described above, you have to accept that (andin some cases the copy constructor and destructor are trivial so it’s notan issue). In addition, the compiler may be able to optimize the code(say, using the named return value optimization) so the price you payfor intuitive, memory-safe and well-behaved code is often a small one.Here’s the final, correct implementation of AddColor(), which returnsTColor by value:TColor TColor::AddColor(TInt aRed, TInt aGreen, TInt aBlue){TColor newColor(aRed+iRed, iGreen+aGreen, iBlue+aBlue);return (newColor); // Correct, return by value}PARAMETERS AND RETURN VALUES307If you return a reference to an object which is local to the scope ofyour function, the object will be released back to the stack whenyour function returns, and the return value will not reference a validobject.