Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

WHAT CAN I CHANGE WITHOUT BREAKING BINARY COMPATIBILITY? 287

// Pass in TColor by reference (4 bytes)

IMPORT_C void Fill(TColor& aBackground);

18.5What Can I Change Without Breaking Binary Compatibility?

You Can Extend an API

You can add classes, constants, global data or functions without breaking compatibility.3 Likewise, a class can be extended by the addition of static member functions or non-virtual member functions. Recall the definition of the direction of compatibility at the beginning of this chapter – any extension of an API is, by its very nature, not forward-compatible.

If you add functions that are exported for external code to use (as described in Chapter 20), you must add the new exports to the bottom of the module definition file (.def) used to determine the ordinal number by which the linker identifies the exported function. You must avoid reordering the list of exported functions. This will break binary compatibility, as described above.

As I discussed earlier, you should not add virtual member functions to classes which may have been subclassed (i.e., externally derivable classes), since this causes the vtable of the deriving classes to be re-ordered.

You Can Modify the Private Internals of a Class

Changes to private class methods that are not exported and are not virtual do not break client compatibility. Likewise for protected methods in a class which is not derivable. However, the functions must not be called by externally-accessible inline methods, since the call inside the inline method would be compiled into external calling code – and would be broken by an incompatible change to the internals of the class. As I discuss in Chapter 21, it is general good practice to restrict, or eliminate, the use of publicly-accessible inline functions, particularly where compatibility is an issue. This is also discussed further in Section 18.6.

Changes to private class member data are also permissible, unless it results in a change to the size of the class object or moves the position of public or protected data in the class object (exposed directly, through inheritance or through public inline ”accessor” methods).

You Can Relax Access Specification

The C++ access specifier (public, protected, private) doesn’t affect the layout of a class and can be relaxed without affecting the data order

3 You should endeavor to avoid any name clashes within the component or others in the global namespace – otherwise the change will be source-incompatible.

288

COMPATIBILITY

of the object. The position of member data in a class object is determined solely by the position of the data as it is specified in the class definition.

It is not sensible to change the access specification to a more restricted form, e.g. from public to private, because, although it does not affect the code, it means that the member data becomes invisible to external clients when previously it was visible. A future change to the component might incorrectly assume that the data has always been private and modify its purpose, affecting external components dependent upon it. In addition, any existing client which accesses that data will no longer be able to do so – a source-incompatible change.

You Can Substitute Pointers for References and Vice Versa

Changing from a pointer to a reference parameter or return type (or vice versa) in a class method does not break binary compatibility. This is because references and pointers can be considered to be represented in the same way by the C++ compiler.

You Can Change the Names of Exported Non-Virtual Functions

Symbian OS is linked purely by ordinal and not by name and signature. This means that it is possible to make changes to the name of exported functions and retain binary, if not source, compatibility.

You Can Widen the Input

Input can be made more generic (”widened”) as long as input that is currently valid retains the same interpretation. Thus, for example, a function can be modified to accept a less derived pointer4 or extra values can be added to an enumeration, as long as it is extended rather than re-ordered, which would change the original values.

You Can Narrow the Output

Output can be made less generic (”narrowed”) as long as any current output values are preserved. For example, the return pointer of a function can be made more derived as long as the new return type applies to the original return value.5 For multiple inheritance, say, a pointer to a class is unchanged when it is converted to a pointer to the first base class in

4 Say class CSiamese derives from class CCat. If the pointer passed to a function was originally of type CSiamese, it is acceptable to change the function signature to take a pointer to the less-derived CCat type.

5 Using the same example of class CSiamese which derives from CCat, if the pointer returned from a function was originally of type CCat, it is acceptable to change the function signature to return a pointer to the more-derived CSiamese type.

BEST PRACTICE: PLANNING FOR FUTURE CHANGES

289

the inheritance declaration order. That is, the layout of the object follows the inheritance order specified.

You Can Apply the const Specifier

It is acceptable to change non-const parameters, return types or the ”this” pointer to be const in a non-virtual function, as long as the parameter is no more complicated than a reference or pointer (i.e., not a reference to a pointer or a pointer to a pointer). This is because you can pass non-const parameters to const functions or those that take const parameters. In effect, this is an extension of the ”widen input” guideline described above.

18.6 Best Practice: Planning for Future Changes

Don’t Inline Functions

As described earlier, and later in Chapter 21 which discusses good coding practice, an inline function is compiled into the client’s code. This means that a client must recompile its code in order to pick up a change to an inline function.

If you want to use private inline methods within the class, you should ensure that they are not accessible externally, say by implementing them in a file that is accessible only to the code module in question.

Using an inline function increases the coupling between your component and its dependents, which should generally be avoided.

Don’t Expose Any Public or Protected Member Data

The position of data is fixed for the lifetime of the object if it is externally accessible, either directly or through derivation. You should aim to encapsulate member data privately within a class and provide (noninline) accessor functions where necessary. Chapter 20 discusses this in more detail.

Allow Object Initialization to Leave

The steps required to instantiate an object of your class may not currently have the potential to fail, but the class may be extended in future, perhaps to read data from a configuration file which may not succeed, say if the file is corrupt or missing. To allow for an extension of this type, object instantiation should be able to leave safely should this become necessary.

290

COMPATIBILITY

This is a straightforward guideline to adhere to, because the Symbian OS ”two-phase construction” idiom, described in Chapter 4, allows for exactly this type of extensibility:

class CExtensibleClass : public CBase

{

public:

static CExtensibleClass* NewL();

... // Omitted protected:

CExtensibleClass(); // Constructor is externally inaccessible void ConstructL(){}; // For extensibility

private:

... // Omitted };

CExtensibleClass::CExtensibleClass()

{// Implement non-leaving construction code in this function }

CExtensibleClass* CExtensibleClass::NewL()

{// Can later be extended to call ConstructL() if initialization // needs to leave

CExtensibleClass me = new (ELeave) CExtensibleClass(); return (me);

}

Override Virtual Functions that You Expect to Override Later

This will ensure that compatibility is not broken should you later need to override a virtual function which was originally inherited (as described earlier in Section 18.4). If you do not currently need to modify the functions beyond what the base class supplies, the overridden implementation should simply call the base-class function. This will allow the functions to be extended in future. Earlier, I gave the example of CSiamese, deriving from CCat, which inherited the default implementation of the CCat::SleepL() virtual method in version 1.0 but overrode it in version 2.0. The sample code below shows how to avoid this by overriding both virtual functions in version 1.0, although CSiamese::SleepL() simply calls through to CCat::SleepL():

Class CCat : public CBase // Abstract base class

{

public:

IMPORT_C virtual CCat() =0; public:

IMPORT_C virtual void EatL(); // Default implementation IMPORT_C virtual void SleepL();// Default implementation // ...

};

class CSiamese : public CCat // Version 1.0

{