For API / ABI compatibility in many toolchains with the same binary code, it is well known that STL, std::string containers and other standard library classes, such as iostreams, are verboten in public headers. (An exception to this is if you distribute one assembly for each version of the supported toolchains, one provides source code without binaries for compiling end users that are not preferred parameters in this case, or one translates to another container internally, so a different implementation std does not get into the library.)
If you already had a published library API that did not comply with this rule (request to a friend), what is the best way forward, while preserving as much compatibility as possible with previous versions, since I reasonably can and prefer compromise breakdowns, t? I need to support Windows and Linux.
Go to the level of compatibility with the ABI I'm looking for: I don't need to be insanely reliable in the future. Basically, I want to make only one binary library file for several popular Linux distributions for release. (I currently release one for each compiler, and sometimes special versions for a special distribution (RHEL vs Debian). The same applies to MSVC versions - one of the DLLs for all supported versions of MSVC would be ideal.) Secondly, t break the API in the bugfix release, I would like it to be compatible with ABI and replace DLL / SO replace without restoring the client application.
I have three cases with some preliminary sentences modeled after Qt to the extent.
Old public API:
// Case 1: Non-virtual functions with containers void Foo( const char* ); void Foo( const std::string& ); // Case 2: Virtual functions class Bar { public: virtual ~Bar() = default; virtual void VirtFn( const std::string& ); }; // Case 3: Serialization std::ostream& operator << ( std::ostream& os, const Bar& bar );
Case 1: Non-virtual functions with containers
In theory, we can convert std::string to a class very similar to std::string_view , but under our library API / ABI control. It is converted to our library header from std::string , so the compiled library still accepts, but does not depend on the implementation of std::string and is backward compatible:
New API:
class MyStringView { public: MyStringView( const std::string& )
Most client codes that donβt do anything abnormal, like taking a Foo address, will work without change. Similarly, we can create our own replacement std::vector , although in some cases it may incur a copy penalty.
Abseil ToW # 1 recommends starting with the utility code and working instead of launching it in the API. Any other tips or pitfalls here?
Case 2: Virtual Functions
But what about virtual functions? We break backward compatibility if we change the signature. I suppose we could leave the old one in place using final to force the break:
// Introduce base class for functions that need to be final class BarBase { public: virtual ~BarBase() = default; virtual void VirtFn( const std::string& ) = 0; }; class Bar : public BarBase { public: void VirtFn( const std::string& str ) final { VirtFn( MyStringView( str ) ); } // Add new overload, also virtual virtual void VirtFn( MyStringView ); };
Now redefinition of the old virtual function will be interrupted at compile time, but calls to std::string will be automatically converted. Overrides should use the new version and break at compile time.
Any clues or pitfalls here?
Case 3: Serialization
I'm not sure what to do with iostreams. One option, at the risk of some inefficiency, is to define them in a line and redirect them through the lines:
MyString ToString( const Bar& );
If I made ToString() virtual function, then I can iterate over all Bar objects and call user overrides, since it depends only on MyString objects, which are defined in the header, where they interact with std objects, such as a stream.
Thoughts, pitfalls?