I have a recurring problem with a statically linked library I've written (or in some cases, code was accumulated from open sources).
This library, MFC Toolbox Library by name, has a lot of free functions, classes, and so on which support MFC programming, Win32 API programming, as well as the venerable C-library and newer C++ standard library.
In short, this is a working library with tools that apply to my daily work, that I've accumulated over more than a decade, and is indispensable to our products. As such, it has a rich mixture of utilities and augmentations for all of these various technologies, and often internally mixes usage of all of these technologies to create further support.
For example, I have a String Utilities.h and String Utilities.cpp which provide a plethora of string-related free-functions and even a class or two.
And often I find that I have a pair of functions, one that works without need of MFC or its CStrings, and another sibling function that does need these things. For example:
////////////////////////////////////////////////////////////////////////
// Line Terminator Manipulation
////////////////////////////////////////////////////////////////////////
// AnsiToUnix() Convert Mac or PC style string to Unix style string (i.e. no CR/LF or CR only, but rather LF only)
// NOTE: in-place conversion!
TCHAR * AnsiToUnix(TCHAR * pszAnsi, size_t size);
template <typename T, size_t size>
T * AnsiToUnix(T (&pszBuffer)[size]) { return AnsiToUnix(pszBuffer, size); }
inline TCHAR * AnsiToUnix(Toolbox::AutoCStringBuffer & buffer) { return AnsiToUnix(buffer, buffer.size()); }
// UnixToAnsi() Converts a Unix style string to a PC style string (i.e. CR or LF alone -> CR/LF pair)
CString UnixToAnsi(const TCHAR * source);
As you can see, AnsiToUnix doesn't require a CString. Because Unix uses a single Carriage Return as a line terminator, and Windows ANSI strings use CR+LF as a line terminator, I am guaranteed that the resulting string will fit within the original buffer space. But for the reverse conversion, the string is almost guaranteed to grow, adding an extra LF for every occurrence of a CR, and hence it is desirable to use a CString (or perhaps a std::string) to provide for the automatic growth of the string.
This is just one example, and in and of itself, is not too beastly to consider converting from CString to std::string to remove the dependency upon MFC from that portion of the library. However, there are other examples where the dependency is much more subtle, and the work greater to change it. Further, the code is well tested as is. If I go and try to remove all MFC dependencies, I am likely to introduce subtle errors to the code, which would potentially compromise our product, and exacerbate the amount of time needed on this essentially not-strictly-necessary task.
The important thing I wanted to get across is that here we have a set of functions, all very related to one another (ANSI->UNIX, UNIX->ANSI), but where one side uses MFC, and the other only uses character arrays. So, if I am trying to provide a library header that is as reusable as possible, it is desirable to break out the functions which are all dependent on MFC into one header, and those which are not into another, so that it is easier to distribute said files to other projects which do not employ MFC (or whatever technology is in question: e.g. It would be desirable to have all functions which don't require Win32 headers - which are simply augmentations to C++, to have their own header, and etc.).
My question to all of you, is how do you manage these issues - Technology dependency vs. related functions all being in the same place?
How do you break down your libraries - divide things out? What goes with what?
Perhaps it is important to add my motivation: I would like to be able to publish articles and share code with others, but generally speaking, they tend to use portions of the MFC Toolbox Library, which themselves use other parts, creating a deep web of dependencies, and I don't want to burden the reader / programmer / consumer of these articles and code-projects with so much baggage!
I can certainly strip out just the parts needed for a given article or project, but that seems like a time-intensive and pointless endeavor. It would be much more sensible, to my mind, to clean up the library in such a way that I can more easily share things without dragging the entire library with me. i.e. Reorganize it once, rather than having to dig things out each and every time...
Here's another good example:
UINT GetPlatformGDILimit()
{
return CSystemInfo::IsWin9xCore() ? 0x7FFF : 0x7FFFFFFF;
}
GetPlatformGDILimit() is a fairly generic, utilitarian free function. It really doesn't have anything to do with CSystemInfo, other than as a client. So it doesn't belong in "SystemInfo.h". And it is just a single free-function - surely nobody would try to put it in its own header? I have placed it in "Win32Misc.h", which has an assortment of such things - free functions mostly which augment the Win32 API. Yet, this seemingly innocuous function is dependent upon CSystemInfo, which itself uses CStrings and a whole slew of other library functions to make it able to do its job efficiently, or in fewer lines of code, or more robustly, or all of the above.
But if I have a demo project that refers to one or two functions in the Win32Misc.h header, then I get into the bind of needing to either extract just the individual functions that project needs (and everything that those functions depends upon, and everything those entities depend upon, etc.) -- or I have to try to include the Win32Misc.h and its .cpp - which drags even more unwanted overhead with them (just in order for the demo project to compile).
So what rule of thumb do folks use to guide yourselves as to where to draw the line - what goes with what? How to keep C++ libraries from becoming a dependency tree from hell? ;)