There are many portability issues with C++, which is only because of
lack of it's standardization at the binary level.
I don't think it's quite this simple. The answers provided already provide excellent rationale as to the lack of focus on standardization, but C++ may be too rich of a language to be well-suited to genuinely compete with C as an ABI standard.
We can go into name mangling resulting from function overloading, vtable incompatibilities, incompatibilities with exceptions throw across module boundaries, etc. All of these are a real pain, and I do wish they could at least standardize vtable layouts.
But an ABI standard isn't just about making C++ dylibs produced in one compiler capable of being used by another binary built by a different compiler. ABI is used cross-languages. It would be nice if they could at least cover the first part, but there's no way I see C++ ever truly competing with C at the sort of universal ABI level so crucial for making the most widely-compatible dylibs.
Imagine a simple pair of functions exported like this:
void f(Foo foo);
void f(Bar bar, int val);
... and imagine Foo
and Bar
were classes with parameterized constructors, copy constructors, move constructors, and non-trivial destructors.
Then take the scenario of a Python/Lua/C#/Java/Haskell/etc. developer trying to import this module and use it in their language.
First we'd need a name mangling standard for how to export symbols utilizing function overloading. This is an easier part. Yet it shouldn't really be name "mangling". Since users of the dylib have to look up symbols by name, the overloads here should lead to names which don't look like a complete mess. Maybe the symbol names could be like "f_Foo"
"f_Bar_int"
or something of that sort. We'd have to be sure they can't clash with a name actually defined by the developer, perhaps reserving some symbols/characters/conventions for ABI usage.
But now a tougher scenario. How does the Python developer, for example, invoke move constructors, copy constructors, and destructors? Maybe we could export those as part of the dylib. But what if Foo
and Bar
are exported in different modules? Should we duplicate the symbols and implementations associated in this dylib or not? I'd suggest we do, since it might get really annoying really fast otherwise to start having to get tangled up in multiple dylib interfaces just to create an object here, pass it here, copy one there, destroy it here. While the same basic concern could somewhat apply in C (just more manually/explicitly), C tends to avoid this just by nature of the way people program with it.
This is just a small sample of the awkwardness. What happens when one of the f
functions above throws a BazException
(also a C++ class with constructors and destructors and deriving std::exception) into JavaScript?
At best I think we can only hope to standardize an ABI that works from one binary produced by one C++ compiler to another binary produced by another. That would be great, of course, but I just wanted to point this out. Typically accompanying such concerns to distribute a generalized library that works cross-compilers is also often the desire to make it really generalized and compatible cross-languages.
Suggested Solution
My suggested solution after struggling to find ways to use C++ interfaces for APIs/ABIs for years with COM-style interfaces is to just become a "C/C++" (pun) developer.
Use C to create those universal ABIs, with C++ for the implementation. We can still do things like export functions that return pointers to opaque C++ classes with explicit functions to create and destroy such objects on the heap. Try to fall in love with that C aesthetic from an ABI perspective even if we're totally using C++ for the implementation. Abstract interfaces can be modeled using tables of function pointers. It's tedious to wrap this stuff up into a C API, but the benefits and the compatibility of the distribution that comes with that will tend to make it very worthwhile.
Then if we don't like using this interface so much directly (we probably shouldn't at least for RAII reasons), we can wrap it all we want in a statically-linked C++ library we ship with the SDK. C++ clients can use that.
Python clients won't want to use either a C or C++ interface directly as there's no ways to make those pythonique. They'll want to wrap it up into their own pythonique interfaces, so it's actually a good thing that we're just exporting a bare minimum C API/ABI to make that as easy as possible.
I think a lot of the C++ industry would benefit from doing this more rather than trying to stubbornly ship COM-style interfaces and so forth. It would also make all of our lives easier as users of these dylibs to not have to fuss about with awkward ABIs. C makes it simple, and the simplicity of it from an ABI perspective allows us to create APIs/ABIs that work naturally and with minimalism for all kinds of FFIs.