Orthodox C++ (sometimes referred as C+) is minimal subset of C++ that improves C, but avoids all unnecessary things from so called Modern C++. It's exactly opposite of what Modern C++ suppose to be.
Back in late 1990 we were also modern-at-the-time C++ hipsters, and we used latest features. We told everyone also they should use those features too. Over time we learned it's unnecesary to use some language features just because they are there, or features we used proved to be bad (like RTTI, exceptions, and streams), or it backfired by unnecessary code complexity. If you think this is nonsense, just wait few more years and you'll hate Modern C++ too ("Why I don't spend time with Modern C++ anymore" archived LinkedIn article).
Code base written with Orthodox C++ limitations will be easer to understand, simpler, and it will build with older compilers. Projects written in Orthodox C++ subset will be more acceptable by other C++ projects because subset used by Orthodox C++ is unlikely to violate adopter's C++ subset preferences.
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
- C-like C++ is good start, if code doesn't require more complexity don't add unnecessary C++ complexities. In general case code should be readable to anyone who is familiar with C language.
- Don't do this, the end of "design rationale" in Orthodox C++ should be immedately after "Quite simple, and it is usable. EOF".
- Don't use exceptions.
Exception handling is the only C++ language feature which requires significant support from a complex runtime system, and it's the only C++ feature that has a runtime cost even if you don't use it – sometimes as additional hidden code at every object construction, destruction, and try block entry/exit, and always by limiting what the compiler's optimizer can do, often quite significantly. Yet C++ exception specifications are not enforced at compile time anyway, so you don't even get to know that you didn't forget to handle some error case! And on a stylistic note, the exception style of error handling doesn't mesh very well with the C style of error return codes, which causes a real schism in programming styles because a great deal of C++ code must invariably call down into underlying C libraries.
- Don't use RTTI.
- Don't use C++ runtime wrapper for C runtime includes (
<cstdio>
,<cmath>
, etc.), use C runtime instead (<stdio.h>
,<math.h>
, etc.) - Don't use stream (
<iostream>
,<stringstream>
, etc.), use printf style functions instead. - Don't use anything from STL that allocates memory, unless you don't care about memory management. See CppCon 2015: Andrei Alexandrescu "std::allocator Is to Allocation what std::vector Is to Vexation" talk, and Why many AAA gamedev studios opt out of the STL thread for more info.
- Don't use metaprogramming excessively for academic masturbation. Use it in moderation, only where necessary, and where it reduces code complexity.
- Wary of any features introduced in current standard C++, ideally wait for improvements of those feature in next iteration of standard. Example
constexpr
from C++11 became usable in C++14 (per Jason Turner cppbestpractices.com curator)
Due to lag of adoption of C++ standard by compilers, OS distributions, etc. it's usually not possible to start using new useful language features immediately. General guideline is: if current year is C++year+5 then it's safe to start selectively using C++year's features. For example, if standard is C++11, and current year >= 2016 then it's probably safe. If standard required to compile your code is C++17 and year is 2016 then obviously you're practicing "Resume Driven Development" methodology. If you're doing this for open source project, then you're not creating something others can use.
UPDATE As of January 14th 2022, Orthodox C++ committee approved use of C++17.
- Embedded C++
- Nominal C++
- Sane C++
- Why Your C++ Should Be Simple
- C++, it’s not you. It’s me.
- "Keep It C-mple" Alexander Radchenko Sydney C++ Meetup
- A dialect of C++
- Any C source that compiles with C++ compiler.
- DOOM 3 BFG
- Qt (when built with no-rtti, no-exceptions)
- dear imgui
- bgfx
- TheForge
- Oryol
- Network Next SDK
std::unique_ptr should definitely be blamed for that since it makes the abuse so easy.
https://releases.llvm.org/13.0.0/projects/libcxx/docs/DesignDocs/UniquePtrTrivialAbi.html
std::format is a HUGE issue.
https://github.com/bminor/glibc/blob/master/stdio-common/vfprintf-internal.c
From the experience of glibc, they even told you it is bugged. Even after 30 years of development. Anytime you have a bug with std::format, the attacker can do whatever they want.
Log4j is exactly the format string vulnerability same with std::format. The history has proven format string is a historical mistake and a huge security hazard no matter how you deal with it.
https://www.netsparker.com/blog/web-security/format-string-vulnerabilities/
What you really need is none format string solutions for daily work. Sure you might argue sometimes you need to use them for localizations, but there is no reason you need powerful functionalities of std::format does, if your argument is just localizations. Things like width and floating point precisions should NEVER EVER be part of localization format string and the functionalities should just NEVER fail.
Just
instead of
Nobody said iostream should just be immediately removed or something. It has to go through a transition period.
In fact, fast_io does provide compatibility layer for C++ stream.
https://github.com/tearosccebe/fast_io/blob/c6d7e9d73bd3dfccc995b39666db836e885400a6/include/fast_io_legacy_impl/filebuf/filebuf_file.h#L39
Guess what? No unique_ptr nonsense. Not only it provides an RAII wrapper for std::filebuf, but allows you to construct std::filebuf from NT, win32 handle, posix fd and FILE*. It also allows you to get HANDLE, fd and FILE* from std::filebuf which is something C++ fstream does not provide. They do not expose native handle and you cannot use them with OS apis.
You have to do something to iostream finally.
https://www.youtube.com/watch?v=grWw7j54KEY
Fail to link is exactly the issue. You won't be able to easily what's wrong with it. You won't see "malloc symbol does not defined", instead it might show things like it requires _sbrk(), _fork() syscalls etc. How are you going to implement them if you do not know how those functionalities work?
Using std::unique_ptr as a manager for a non-heap resource is EXACTLY the issue. Why not just write a new c_file class instead of using std::unique_ptr<FILE,std::function<decltype(fclose)>>?
You think code like
is more readable than just defining your only class and doing all the error handling inside the constructors? Just writing a new class not only making the code more readable and less error prone but also faster.
c_file cf("a.txt",open_mode::out);
https://github.com/tearosccebe/fast_io/blob/c6d7e9d73bd3dfccc995b39666db836e885400a6/include/fast_io_legacy_impl/c/impl.h#L974
I can even add a lot of other functionalities like allowing constructing c_file with a directory entry (that would avoid TOCTOU, another functionality of POSIX what C++ std::filesystem lacks of) and allowing c_file to be constructed with NT handle, win32 HANDLE and POSIX fd. I can even provide functionalities for setting permissions. Are those things you can easily do with your unique_ptr abuse without crazy amount of factory functions?
In reality, people just write shit like
std::unique_ptr<FILE,std::function<decltype(fclose)>>
, causing performance issues and security hazard (due to introducing of dynamic dispatch silently).There is no way you can use std::unique_ptr to manage non-heap resource without paying for extra overhead. Even you use things like lambda etc, it will bloat binary size for no reason due to compiler would treat the lambda with function bodies as different types.
And how do you deal with resources that are not pointers with unique_ptr? Like POSIX fd? unique_ptr in reality is always just an abuse.
https://youtu.be/zxFNnOFYCkI?t=888
Not mentioning a lot of people abuse it for creating data structures like linked list, leading to stack overflow because of the recursive calls of destructors.
Conclusion: using unique_ptr is always just wrong.
It is so easy to enter the place you do not want to go. Particularly in the hosted environment. Since it throws EH, it would randomly bloat binary size and you have no way to detect them without using a freestanding toolchain. I was to fix a bug that relates to using std::string_view' substr, causing serious binary bloat. What i end up is just banning entire std::string_view, because it is too awful.
Not mentioning with other problems like silently introducing std::string as dependency in the header files, causing enormous amount of pain of compilation time.