Saturday, June 11, 2005
Tip 7: For API design, use the descriptor base classes as parameters and return values.
Callers of your API will not want to be constrained, say, to have to use a TBuf, just because your function requires it. And your functions shouldn't care what type of descriptor is passed to them, as long as any modifiable descriptor has a sufficient data area to extend into, as necessary.
One good reason for this 'agnosticism' is that, if you need to change the implementation later, and have exposed the descriptor type at the API level, you make it harder for yourself to change the code without affecting the API. You really don't want to have to ask your clients to change their code because you've broken source compatibility.
And anyway, unless you’re taking ownership, if you're implementing a function you shouldn’t need to know if an incoming parameter is stack- or heap-based. Its location and memory layout should be irrelevant. Likewise if you're calling a method, and receiving a descriptor as a return value, you don't need to know what type it is, unless it is a heap descriptor for which you become responsible for cleanup.
Thus, when defining functions you should always use the abstract base classes as parameters or return values. For example, class RFile defines straightforward file read and write methods as follows:
IMPORT_C TInt Write(const TDesC8& aDes);
IMPORT_C TInt Read(TDes8& aDes) const;
The descriptor to write to the file is a constant descriptor, while to read from the file into a descriptor requires the parameter to be modifiable (the maximum length of the modifiable descriptor determines how much file data can be read into it).
When writing a function which receives a modifiable descriptor you don’t have to know whether it has sufficient memory for an operation, such as Append(), since the descriptor methods themselves make the check and panic if the operation would cause an overflow. However, you may not want the descriptor methods to panic if the caller has not got a large enough descriptor, because sometimes they cannot know the maximum length required. (Remember, a panic is terminal and in effect causes a 'crash' which can be pretty ugly).
As an alternative, you could perform your own bounds checking before calling a method on the incoming descriptor, then return an error or leave when you need to indicate to the caller that the descriptor's maximum length is insufficient. In addition, you could write the required length into the 'too short' descriptor supplied (assuming it is at least 4 bytes in length) so the caller can use it to allocate a descriptor of the correct minimum length.
There are occasions when you pass and return heap descriptors, HBufC, by reference or pointer. I'll go into this in a separate post.
See also, this FAQ ("How do I use descriptors as parameters?") and this one, which covers returning them ("How do I use heap descriptors as return types?").