Thursday, August 23, 2007
Pay it Forward: Part 2
When writing C++ classes and interfaces (e.g. abstract classes), do your compiler a favor and declare each class in its own header file whenever you can. Trust me, it'll pay you back later.
Rule of thumb:
Now, here's an example of how not to use your nested types:
... actually no. You can get on with your life. Your incremental build is now done because you've paid it forward!
Rule of thumb:
If you ever need to use the would-be nested class without using the nesting class, then your class should not be nested.To see how nested classes should be used, look at std::string::iterator. It's a nested type (Okay, okay, it could be a nested typedef that could alias a non-nested class. From outside std::string, it looks like a nested class and that's all that matters.) that's useless without its nesting class std::string.
Now, here's an example of how not to use your nested types:
AddressBook.h
// Forward type declarationWith the the above implementation, whenever someone wants to receive notification from the AddressBook, they need to include the AddressBook itself and derive from AddressBook::IContactEventSink. Doing this has the following down sides:
class Contact; // Remember this?
class AddressBook
{
public:
// Interface to be implemented by objects wanting AddressBook notifications.
class IContactEventSink
{
public:
virtual void onContactChanged(Contact const& oldContact, Contact const& newContact) = 0;
};
...
};
- Your IContactEventSink interface is now coupled with the AddressBook class. What if you want to re-use IContactEventSink for your CorporateAddressBook class?
- You won't be able to forward-declare the IContactEventSink class because nested classes can't be forward declared (See Pay it Forward - Part 1 for why you'd want to do this).
- Every time anything changes in the AddressBook.h file, you'll have to waste time rebuilding all files using the IContactEventSink.
IContactEventSink.h
// Forward type declarationThen, hit "Build", sit back and wait to...
class Contact; // Yup, even here.
class IContactEventSink
{
public:
virtual void onContactChanged(Contact const& oldContact, Contact const& newContact) = 0;
};
... actually no. You can get on with your life. Your incremental build is now done because you've paid it forward!
Labels: c++, compiler, nested class, pay it forward
Wednesday, May 30, 2007
Pay it Forward: Part 1
When writing C++ code, do your compiler a favor and use forward declarations whenever possible: it will pay you back later... and more.
Forward declarations will...
Here are some rules of thumb that will help you determine whether a full definition is necessary for a certain type:
Pay it forward... and wait to be paid back.
Forward declarations will...
- ... decouple classes that depend on each other.
- ... improve incremental build times considerably when changing the header file declaring the class where the forward declaration was used.
- ... increase the portability of your code.
AddressBook.h
//#include "Contact.h" // NO! Do this in the .cpp file instead. Unless youIf the user of this class doesn't use the member functions that require the undefined type, he will not be required to include the header that defines it. The user will only need to do this if he uses those member functions.
// really need the definition of the class.
// Forward type declaration
class Contact; // YES!
class AddressBook
{
public:
// ...
/** Ways you can use the declaration without having the definition **/
// Member functions input parameters.
void addContact(unsigned int id, Contact const& newContact);
void addContact(unsigned int id, Contact const* newContact);
// Member functions output parameters.
void getContact(unsigned int id, Contact& contact);
void getContact(unsigned int id, Contact* contact);
// These cases are a bit more obscure than the others, but it is legal and
// still works.
void addContact(unsigned int id, Contact newContact);
Contact getContact(unsigned int id);
private:
// Member variables
Contact* m_contactPointer;
Contact& m_contactRef;
/** These some cases where the compiler needs the definition. **/
Contact::Address m_address;
static const size_t ms_contactSize = sizeof(Contact);
Contact m_contact;
Contact m_contactArray[10];
/** Here are some cases that often work with different STL implementations and
compilers, but are illegal. **/
std::vector<Contact> m_contacts;
std::auto_ptr<Contact> m_contactAutoPtr;
};
Here are some rules of thumb that will help you determine whether a full definition is necessary for a certain type:
- Enums and typedefs types cannot be forward-declared.
- You will be able to use your forward-declared type as a template parameter if and only if the templated type doesn't need the full definition.
- You will need the full definition to get the size of a type, use any constructors or detructors, or access members (types scoped at class level).
Pay it forward... and wait to be paid back.
Labels: c++, compiler, optimization, pay it forward