What is the best way to separate utility functions in a library to maximize reuse?

I have a recurring problem with a statically linked library that I wrote (or, in some cases, the code was accumulated from open sources).

This library, the MFC Tool Library by name, has many free features, classes, etc. that support MFC programming, Win32 API programming, as well as the venerable C library and the newer C ++ Standard Library.

In short, this is a working library with tools that apply to my daily work, which I have accumulated over a decade and is indispensable for our products. Thus, it has a rich mixture of utilities and additions for all of these various technologies, and often internally mixes the use of all these technologies to create additional support.

For example, I have String Utilities.h and String Utilities.cpp that provide many free string-related functions, and even a class or two.

And often, I find that I have a couple of functions, one of which works without the need for MFC or CStrings, and another sibling function that really needs these things. For instance:

//////////////////////////////////////////////////////////////////////// // Line Terminator Manipulation //////////////////////////////////////////////////////////////////////// // AnsiToUnix() Convert Mac or PC style string to Unix style string (ie 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 (ie CR or LF alone -> CR/LF pair) CString UnixToAnsi(const TCHAR * source); 

As you can see, AnsiToUnix does not require a CString. Since Unix uses a single carriage return as a line terminator, and ANSI Windows lines use CR + LF as a line terminator, I guarantee that the resulting line will fit in the original buffer space. But for the inverse conversion, the string is almost guaranteed to grow, adding an extra LF for each CR occurrence, and therefore it is advisable to use CString (or perhaps std :: string) to ensure the string grows automatically.

This is just one example and not in itself too brutal to consider converting from CString to std :: string to remove the MFC dependency from this part of the library. However, there are other examples where the dependence is much more subtle and the work is more to change it. In addition, the code is well tested as is. If I leave and try to remove all the MFC dependencies, I will most likely put subtle errors into the code that could potentially compromise our product and aggravate the amount of time required for this essentially unnecessarily necessary task.

The important thing I wanted to get is that here we have a set of functions, all very related to each other (ANSI-> UNIX, UNIX-> ANSI), but where one side uses MFC and the other only uses arrays characters. Therefore, if I try to provide a library title that can be used as many times as possible, it is advisable to split functions that all depend on MFC into one header and those that are not in another, so itโ€™s easier to distribute the specified files in other projects to which do not use MFC (or any other technology): for example, it would be desirable to have all functions that do not require Win32 headers, which simply complement C ++, have their own header, etc.).

My question is for all of you: how do you deal with these problems: is technology dependent on related functions in one place?

How do you split your libraries - share things? What is happening with what?

Perhaps itโ€™s important to add my motivation: I would like to be able to publish articles and share codes with others, but as a rule, they usually use parts of the MFC Toolbox, which themselves use other parts, creating a deep network of dependencies, and I I donโ€™t want to burden the reader / programmer / consumer with these articles and code projects with so much baggage!

I can, of course, cut out only the parts necessary for this article or project, but it seems like a time-consuming and meaningless work. In my opinion, it would be much wiser to clean the library so that I can share things more easily without dragging the entire library with me. those. reorganize it once, instead of digging things up every time ...


Here is another good example:

 UINT GetPlatformGDILimit() { return CSystemInfo::IsWin9xCore() ? 0x7FFF : 0x7FFFFFFF; } 

GetPlatformGDILimit () is a fairly general, utilitarian function. It really has nothing to do with CSystemInfo, except as a client. Therefore, it is not included in "SystemInfo.h". And this is just one free feature - will nobody really try to put it in their own header? I put it in "Win32Misc.h", which has a lot of these things - free features that basically complement the Win32 API. However, this seemingly harmless function depends on CSystemInfo, which itself uses CStrings and a host of other library functions to make it able to efficiently perform its work either in fewer lines of code or more reliably 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 a binding to the need to either extract only the individual functions that the project needs (and everything that these functions depend on, and all these objects depend, etc.) - or should I try to turn on Win32Misc.h and its .cpp - which drag even more unwanted overhead from them (just so that the demo project can compile).

So, what is a rule of thumb used by people to behave about where to draw a line - what is connected with this? How to save C ++ libraries as a dependency tree from hell ?;)

+4
source share
3 answers

Personally, I would break the functionality. String manipulations in one library. Integral types in another (except maybe char put this on lib line)

Of course, I would leave the platform dependent material regardless of the platform. Supplier specific components from a particular vendor. This may require two or even three string libraries.

Perhaps you could use the paradigm of โ€œis MFC required?โ€. everything that mfc requires must be shared. Then go to the "Does windows require" section to do some splitting again. etc...

Undoubtedly, some projects will require that all libraries be compiled in VC ++ and run only in windows, which is exactly what happens. Other projects will be happy to compile on linux, using only a subset of the libraries compiled with gcc.

DC

+1
source

If you use only general types of conformations in your public interfaces and keep interfaces separate from implementations, this becomes a problem without problems.

Keep in mind that when you enter a function like this:

 std::string ProcessData(); 

... and put the source code for this module separately from the code that will call it (for example, in a DLL), you violate the edits of a separate-interface-from-implementation. This is because STL is a source code library, and each compiler that uses your library functions can and will have different implementations and different binary layouts for the utilities you use.

0
source

In a very vague answer, KISS is the best policy. However, the code seems to have gone too far and reached a point of no return. This is sad, because what you would like to do is separate libraries that are autonomous objects, which means that they are not dependent on any external things. You are creating an MFC helper library and another library for other helpers or something else. Then you decide which ones you need and when. All dependencies are in each library, and they are autonomous.

Then it is just a question of which library to include or not.

Also, using the condition includes in the header files, works well if you want certain things in certain scenarios. However, I'm still not quite sure if I interpreted the problem correctly.

0
source

Source: https://habr.com/ru/post/1300782/


All Articles