3 ms·
I and my colleague ported a mature project (the fish shell) from C to C++, over a period of about a year, so our experiences are relevant. Like many projects, f
by ridiculous_fish 13y ago
I and my colleague ported a mature project (the fish shell) from C to C++, over a period of about a year, so our experiences are relevant. Like many projects, fish uses C++ as mainly a better C, without embracing exotic templates, exceptions, smart pointers, boost, or other high-powered features. Most of Zed’s points do not apply to this style of programming, and anyone considering a port to C++ ought to consider it.
C++ as a better C does impose some costs, such as longer compile times, more cryptic error messages, nastier backtraces, and losing compatibility with some older systems. But these are tolerable, and on balance the benefits are worth it. In one case, fish strayed and used shared_ptr, which caused compatibility pain. This suggests we’ve hit a sweet spot, where if we adopted more C++ features we’d start to see a greatly increased cost.
Just using std::string would reduce the mm noise and make the code easier to read and reduce the chances of memory leaks.
This is indeed what we found. The memory management simplification was a huge deal, and that drove my decision for the port. However, the big win came not from the specifics of C++'s memory management, but from its regularization: imposing a single memory management scheme.
Like most C projects, the shell was rife with char . When you see a char , you can't immediately know who owns the string, who is responsible for deallocating it, and how it should be deallocated. std::string lets you reason locally about the code, but so too do C libraries such as GObject, CFString, etc. C++ won out over these alternatives because it's more widely supported and has a deep pool of potential contributors. But the C++ "feature" that mattered most was not that its memory management scheme was better, but that it existed.
C is really the best because it is the lowest friction access to all the POSIX stuff you need
This is a big concern that disqualifies most other languages. For example, Go or Haskell cannot easily do checks like `#ifdef VDSUSP`, and have no guaranteed semantics for threads after fork. C is undisputed king here! But C++ doesn’t impose much additional friction as long as you stick to the POSIX primitives. e.g. pthread_* instead of std::thread or std::lock. There is no “huge bother.”
The semantics of destructors are incredibly confusing, especially with exceptions.
Destructors are indeed terrifying when in the direct gaze of a fully operational C++ battlestation. However they are simpler in a suitably sane subset of C++. The fish shell, for its part, disallows all exceptions, even compiling with -fno-exceptions for a binary size savings.
No matter what, unless the language has a GC built-in, then C will have simpler and more predictable memory usage.
As I argued above, the important aspect here is not the choice of language, but the choice of memory management scheme. It is possible to make memory management simple and predictable, or instead ad-hoc and haphazard, in both C and C++. The key difference is that C++ comes with a scheme built-in (which you may or may not choose to use), while C does not. I don’t know how mongrel manages memory, but the “no matter what” claim is obviously wrong.
I couldn't imagine trying to use the string output stream stuff to craft messages and things.
Indeed: wstringstream is a big memory hog! But choosing C++ does not commit you to using the stream formatting syntax, nor does it preclude using printf-style formatting. fish uses printf-style formatting everywhere.
On the flip side, the simple task of appending two strings is awkward in C. Either you have a fixed size buffer, or you commit to some string object type, which then requires manual copying and cleanup. C++’s strings have flaws, but they definitely simplify string handling compared to C.
Even something as simple as casting a float to an int is a pain in the ass involving some weird template like syntax that frankly makes no sense.
C++ typecasts are mainly designed to support exotic template usage, and they are indeed weird and onerous when using C++ as a better C (my pet example is `static_cast<size_t>(-1)`). C++ programmers also promote the C++ casts as “safer” or “better”, and characterize the C-style casts as “unsafe” and “legacy.” Score one for Zed.
Leaving it off here, but I hope I showed how Zed’s points apply to using C++ in all its glory, and that “C++ as a better C” is totally viable for portable, POSIX-using software.
- pmelendez 13y ago>"Leaving it off here, but I hope I showed how Zed’s points apply to using C++ in all its glory" All you said make sense but some points are still arguably. The thing is that C++ feature set is big enough so any combination of them would probably be a good fix for a given project. For instance, you showed some advantages of using “C++ as a better C”, but I had seen horrible bugs originated by a careless use of a void* that could be prevented by using templates. So I would say that the experience change depending of the project requirements. Also, about the C++ cast style thing, it is verbose indeed but it is almost like that on purpose, Stroustrup really wanted to discourage casting and in a lot of cases I am agreed with him, most casts can be avoided.
- aidenn0 13y agoI'm a mongrel2 developer, and we don't use bare char pointers for strings either, in favor of the bstring library. Zed was off the opinion that it is superior to the c++ std:: string.
- cpeterso 13y agoIn contrast, Rust recently migrated the non-Rust parts of its native runtime from C++ to straight C. I believe the motivation as simplicity and to avoid any surprises with external linkage with non-Rust libraries. https://github.com/mozilla/rust/commit/508b7b9 https://github.com/mozilla/rust/commit/508b7b9
- cpeterso 13y agoYou call C++ typecasts onerous, but does that mean you don't use them? I use them (because more C++ thumbscrews must be better), but, in my career, I think they have found only 2-3 compile-time bugs that would have been hidden by C typecasts. I think the C++ function-call-style typecasts (e.g. `size_t x = size_t(-1)`) are pretty elegant.