Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Orthodox C++

Orthodox C++

What is Orthodox C++?

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.

Why not Modern C++?

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).

Why use Orthodox C++?

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.

Hello World in Orthodox C++

#include <stdio.h>

int main()
{
    printf("hello, world\n");
    return 0;
}

What should I use?

  • 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.
  • 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.
  • Don't use metaprogramming excessively for academic masturbation. Use it in moderation, only where necessary, and where it reduces code complexity.

Is it safe to use any of Modern C++ features yet?

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.

Any other similar ideas?

Code examples

πŸ‘

@ghost

ghost commented Jan 16, 2016

Yes please.

oblitum commented Jan 16, 2016

I think you should be more clear regarding "anything that allocates memory". What about "anything that allocates dynamic memory", or memory from the heap, or from free storage. If that is what you mean, maybe you're saying to not use std::array too, regarding memory I mean.

Owner

bkaradzic commented Jan 16, 2016

@oblitum Memory from heap, stack is fine. But it's general because a lot of things allocate memory in STL.

Fixed a typo in the word "adoption" here: https://gist.github.com/elvman/c5b5f081857089ccf695

Owner

bkaradzic commented Jan 17, 2016

@elvman πŸ‘

Great name for this subset! One question: what are the drawbacks to using the C++ runtime wrappers like cstdio and cstdlib?

bullno1 commented Jan 17, 2016

πŸ‘

Owner

bkaradzic commented Jan 17, 2016

@rgthomas It's just a wrapper, it doesn't do anything useful. Just makes C headers pretend to be C++.

Telling people not to use std::vector as general advice (as opposed to on some specific platform, or for some specific performance need) may actually be the worst C++ advice I've seen. It's hard to even imagine how many memory leaks vector has prevented, and how many man hours it's saved.

Streams are not particularly modern C++. They might not even exist if variadics weren't so late to the ball game. Variadic print functions look just like printf, perform similarly, but are much safer: https://github.com/cppformat/cppformat.

features we used proved to be bad (like RTTI, exceptions, and streams),

source ?

Kos commented Jan 17, 2016

Agreeing on a particular representation of "string", "dynamic array" and "associative array" is one of the things I consider to pre-conditions for a mature language.

Why RTTI is bad?

Cygon commented Jan 17, 2016

I respectfully disagree with the extremes to which this idea is taken.

RTTI can be abused (not nearly as much as Java/.NET reflection, though), but it can also be a very neat and tidy puzzle piece that improves performance (see: optionally implemented interfaces!). Exceptions (combined with RAII) are the most efficient and clean error handling method programming languages have come up with so far. STL is useful and well thought out, but okay, even with custom allocators it can be challenging to manage micro-allocations and heap fragmentation on weaker platforms, so perhaps in a few cases roll-your-own is okay.

Source: 20 years of professional C++ development full time (while also trying to keep code maintainable by inexperienced developers)

Just some thoughts.

Don't use anything from STL that allocates memory, unless you don't care about memory management.

What do I use instead? Do I have to make my own container(s)? Why shouldn't I use them? If you're going to say "don't use the STL" at least provide some reasoning and a viable alternative.

If I do roll my own containers, does that mean when I'm going to be using open source libraries there's going to be unnecessary duplicate code just for generic containers because they're not using the STL as you suggested?

Don't use C++ runtime wrapper for C runtime includes (, , etc.), use C runtime instead (<stdio.h>, <math.h>, etc.)

Aren't xxxx.h headers not actually apart of the C++ standard? I thought these were depreciated header files. Maybe I'm wrong, but cppreference doesn't seem to have these documented (only the cxxx.h variants).

Don't use exceptions.

I can understand this from a personal preference, and maybe performance issue if working on embedded devices. But imo, in some situations exceptions could be handy. Though I rarely use them myself.

Don't use RTTI.

Okay, I agree with this. Since there's no standard way for a compiler to implement them, and they are usually a sign of bad design. And if you really do need it, you can easily do it with integers identifying which type it is.

Don't use stream (, , etc.), use printf style functions instead.

Why? I don't see a reason why not to. Other than maybe a "performance reason", but I'm fairly sure printf and streams have similar performance. If anything printf is defiantly less safe, unless you make yourself a safer version using templates.

Owner

bkaradzic commented Jan 17, 2016

@sasmaster It's string based. When you use it, it bloats executable with strings of all classes, and it's slow because does string comparisons.

Owner

bkaradzic commented Jan 17, 2016

@jcelerier Source is personal experience.

Owner

bkaradzic commented Jan 17, 2016

@miguelmartin75

But imo, in some situations exceptions could be handy. Though I rarely use them myself.

Exceptions are not some magic that you enable and your code is just exception safe. Style of code, support to be exception safe, and extra complexity is significant. And not worth for "rarely" used cases.

@bkaradzic The weak exception guarantee is extremely easy to get. You just don't leave unowned resources lying around. Unowned resources for more than a few lines are bad news even without exceptions, it may be fine at the time you commit it but if someone else modifies the code they could easily add an early return that causes a memory leak. Code should be as locally correct as possible; this means immediately taking ownership of resources, and this leads to the weak exception guarantee (which is good enough for many applications).

RTTI isn't super fast, but there are plenty of situations where the performance doesn't matter enough for that to be important. I agree that it's not the first tool you should turn to, but you do occasionally run into things that are awkward to do any other way, where you would likely roll your own version of RTTI if you didn't use regular RTTI. This can be ok too if you can justify the man hours for e.g. performance gain, but often just using RTTI will be easiest and least bug prone.

πŸ‘

Your hello world doesn't show any C++ feature,
So it doesn't show how this is different from pure C.

what is your standing on references ?

Owner

bkaradzic commented Jan 18, 2016

@ chafporte

Your hello world doesn't show any C++ feature,
So it doesn't show how this is different from pure C.

Exactly, that's the point. If you don't need anything from C++ then don't use it, just because you want to be different from C.

what is your standing on references ?

They are fine.

what is your standing on references?

Pointless cruft. They're now useful for making C++ code fit nicely together like a puzzle (different constructors, overloading, operators, templates... etc.) but it would've been better if they didn't got introduced in the language at all.

Amen, brother! πŸ‘ :

mosra commented Jan 18, 2016

Good points, although I have to disagree with this one:

Don't use C++ runtime wrapper for C runtime includes (<cstdio>, <cmath>, etc.), use C runtime instead (<stdio.h>, <math.h>, etc.)

Why would I do that? The C++ includes are not just wrappers, they are converting macro-ridden C insanities into proper inline functions and making them overloaded so you don't have to backtrack your types to use proper abs(), fabs(), llabs() or whatnot. That's in my opinion one of the best features that C++ offers on top of C library. And besides that, I really like the idea of the standard library tools prefixed with std:: to distinguish them from other "non-standard-library" code.

  • Qt

What? Qt? You say that I should not use STL containers because STL containers allocate. That's right, but it is possible to get around many of the allocations with some effort. Qt, on the other hand, allocates all the time and you have absolutely no control about that. I had the "pleasure" of using Qt as a backbone for touchscreen UI running 60 FPS on embedded devices and it's impossible to do anything in it without allocating on every second line. Because of their design decisions like D-pointers and UTF-16, even the tiniest empty objects are allocating and you can't even create a mutex or append a integer to pre-reserved string without spotting tons of mallocs in your profiler. Not to mention the crazy MOC and buildsystem hijacking. Qt is really not something I would put here as an example of well-designed library.

πŸ‘

Owner

bkaradzic commented Jan 19, 2016

@mosra

What? Qt? You say that I should not use STL containers because STL containers allocate.

No, I said if you don't care about precise memory management. Qt is usually used for utilities so memory requirements are a little bit relaxed there.

Qt is really not something I would put here as an example of well-designed library.

It's not example of "well-designed", just example of Orthodox C++ code. I personally think Qt is well designed, the most sane UI framework I used (I used MFC, ATL, wxWidgets, etc.)

Maybe some people can write clang extension to only allow this subset of C++.

My main argument against using STL is the insane amount of code bloat that it generates and its associated increase in compilation time due to excessive use of templates. For example, a simple program that spawns some dummy threads and inserts them into an unordered_set yields an almost 1MB .obj file (VC++2015, Win64, Debug build). In contrast, an object file generated from a 300kb (more than 12000 LOC) C++ source file from RBDOOM3 only has a size of 920KB. Using STL is fine for small applications and tools but when it comes to bigger projects and open source libraries I am against STL, extensive template usage and everything else that bloats the code.

In this Orthodox C++ paradigm can we still use inheritance and virtual functions?

DrCroco commented Oct 7, 2016

I'd say that you're not orthodox enough. First of all, there's no real use for STL. Complicated data structures are never generic, they must be carefully designed for each particular task. From the other hand, primitive data structures, such as vector or list, give almost no profit, but are way harder to debug/maintain than hand-crafted linked lists and resizeable arrays. Furthermore, the very idea of a container class is weird. Container is a thing that contains my data, but a class is a thing I can't (well, must not) look into. So, container classes is when I don't see my data. Hell, I don't want to be blind at the debugging time, as debugging is hard enough without it.

Next, there's no such thing as 'safe features from new standards'. All technical standards since around 1995 or so are simply terrorist acts, and these committees that prepare and create new standards are dangerous international terrorist organizations, and in particular, ISO with their policy of keeping standards secret and selling their texts for money, is the most dangerous terrorist organization in the world, way more dangerous that Al Qaeda. ALL they do is bad. So everything they do must be banned right off, without consideration.

I'm nevertheless glad to see that someone in the world sees the things almost as they are. Modern software industry looks like being made of complete idiots, and there must be some way out of this situation.

Well said! +1000

FrankHB commented Jan 11, 2017 edited

It is suspicious to title things with high density of obvious inconsistency with "orthodox".

For example, the following entries are self-contradict:

Don't use C++ runtime wrapper for C runtime includes (, , etc.), use C runtime instead (<stdio.h>, <math.h>, etc.)
Don't use stream (, , etc.), use printf style functions instead.

How to avoid "stream"? In fact, <stdio.h> provides exactly the APIs for streams. One of them is called the standard (text) output stream, i.e. stdout. And printf relies on it implicitly.

And seems you were allowing <cstddef> (which is hardly a kind of "runtime wrapper") ... huh, is this you want?

And... you forget to rule out <streambuf>, etc...

Some are confusing, like:

Don't use anything from STL that allocates memory, unless you don't care about memory management.

About what? STL was never existed in "modern C++". (You were not meaning Stephan T. Lavavej, right?)

FrankHB commented Jan 11, 2017 edited

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.

I am glad to highly praise such ideas, except you youths are taking it too simple - if you really, really want to "selectively" use the language... And "probably" is hardly enough for tasks in reality once you meet nasty implementation regressions.

Seriously, have you ever done such audit in practice? ... e.g. as the basis, by producing something like this.

FrankHB commented Jan 11, 2017 edited

It's not example of "well-designed", just example of Orthodox C++ code. I personally think Qt is well designed, the most sane UI framework I used (I used MFC, ATL, wxWidgets, etc.)

That's false. Typical "orthodox" Qt-based source is even never a well-formed example for any version of C++. Tools like moc have to be pipelined before feeding the source to any sane C++ implementation, otherwise you'll fail to build. As of non-"orthodox" ones... how do you expect any C++ implementation to be able to translate QML or QSS code successfully?

there's a special kind of hell for you guys

kys

Oh man, I had to use Boost Geometry. I'd been away from C++ for a few years and it make me think my brain had dissolved. I got it to do what it's supposed to - and it did it very well. But, damn, did that hurt.

ahoka commented Feb 28, 2017

Don't tell me what to use.

PhilCK commented Jun 3, 2017

πŸ‘

njlr commented Jun 22, 2017 edited

Hmm... I'm not sure I agree entirely.

Some C++ features that make code more readable and lead to fewer bugs:

  • RAII
  • For-each loops (C++ 11)
  • Smart pointers
  • Templated containers
  • auto

+1 for keeping things simple though.

Words of wisdom! Atleast some in the C++ community realise that, a large number of CPP features are unnecessary or garbage at times. Leaning back to C is ideal in my opinion or in this case orthodox C++ πŸ‘

I mostly don't like this, because the text never mentions any of the motivation behind the advice.

C-like C++ is good start

What is "C-like"? This needs to be specified. Manually managing memory with malloc and free is "C-like", yet no C++ code should be doing that. Writing structs with callback fields is also very C-like, yet it's just copying the functionality provided by virtual methods.

Don't do this, the end of "design rationale" in Orthodox C++ should be immedately after "Quite simple, and it is usable. EOF".

Yeah I don't like boost either

Don't use exceptions.

Don't see the point in this. I guess one could make all your functions return an error state and use output parameters everywhere, I don't see the appeal of that, unless you have to interface with code that is exception-unaware. Also, I need to see some proof that exceptions meaningfully affect performance in the non-exceptional scenario.

Don't use RTTI.

Sure, except the cases when you actually need it are so rare that it almost doesn't matter.

Don't use C++ runtime wrapper for C runtime includes (, , etc.), use C runtime instead (<stdio.h>, <math.h>, etc.)

Why?

Don't use stream (, , etc.), use printf style functions instead.

Again, no justification for this. It could be mentioned at least that printf-style is more amenable to i18n.
But then again

std::cerr << my3dvector << "\n";

is better than

fprintf(stderr, "%f %f %f\n", my3dvector[0], my3dvector[1], my3dvector[2]);

Don't use anything from STL that allocates memory, unless you don't care about memory management.

...which is actually most of the time. Instead of blindly following this advice and reinventing the wheel seventeen times a day, understand your use case and make a judgement what would be the best. Yes, if you need your hash table to always occupy a small fixed amount of memory, don't use unordered_map, roll your own implementation with open addressing or something.

Don't use metaprogramming excessively for academic masturbation. Use it in moderation, only where necessary, and where it reduces code complexity.

Most of this stuff isn't even for application developers anyway. People who will most benefit from these features are library authors (i.e. STL). So yeah, I agree with the sentiment.

keebus commented Jun 29, 2017 edited

@nicebyte I disagree. If you're writing low-level data oriented code, virtual classes almost always get in your way. After years of practical low-level professional game engine programming and personal computer science research, I dare to say when you write code like a game engine you end up in a situations where you either have lots of objects that must be updated the frequently and efficiently or you are dealing with large and few entities (e.g. anything called *Manager, *System, etc). In the latter case an opaque struct pointer followed by several API functions that operate on an instance of that (opaque) struct is expressive enough to create a neat abstraction and I'd say much more OOP than what you'd get in C++, as all of its private implementation details (in the .c) are truly hidden from its interface (in the .h) where in C++ you are forced to declare its private members in the class interface (yuck #1). The former, you want to process all those objects in batches, grouping all the operations and common data relevant to a single object type in a "Type" struct. Something you simply cannot do with C++ because virtual tables are hidden and not supposed to be fetched or populated manually (yuck #2).

Want a practical example? Consider writing a raytracer. You could go ahead and make a Shape virtual interface that has an Intersect() method. Then you have a AggregateShape that has a std::vector<Shape*> (yuck #3) and its own Intersect() method that simply calls each child Shape Intersect in turn and returns the closest to the ray origin. Sure this simple enough, but I'd argue not good enough. A better way is, for instance, having a ShapeType struct with a uint32 containing a unique id and a bunch of function pointers such as (* Intersect)(). your AggregateShape can now be implemented with an array of ShapeTypes (using their unique ID as index) and its Intersect() function being arguably considerably faster, as it can be implemented per ShapeType and not per (any) Shape. New iteration, new ShapeType, store the address of its Intersect function in a register, call it in a hot loop for each shape of that type (naturally stored in an array by value, and not by pointer like in the previous case). Oh, and this also uses less memory, since you have removed the vptr from each Shape object.

No matter how hard I try, the more experienced I become, the less attractive C++ gets. Not to mention the various serious design mistakes the language has I will skip because I need to work. Let's just say C++ is a language that is prone to complexity which exponentially leads to even more complexity and bugs.

keebus commented Jun 29, 2017 edited

I understand and agree with OP's arguments, but I believe the "C++ is huge but you just need to use what you need" is fallacious. Every language has an idiomatic form. Yes you can write C-like C++, but that is not idiomatic C++. This is especially troublesome in a large company with many programmers. Coding standards are hard to enforce, and no matter how hard one tries, large codebases tend to have somewhat mixed styles. In other words, I understand and agree with the intentions of using only a subset of C++, but they're still there, and we're paying the cost for them (complex, slow, buggy compilers) even if we don't use them. It is a workaround not a a solution and should not stop us experiment and develop alternatives altogether.

dud3 commented Jul 1, 2017 edited

Sad can't star this gist more than once.

nicebyte commented Jul 6, 2017 edited

@keebus I want to address your reply.

First, I reject the premise that having to declare private members in the header is somehow defective. Knowing the size of the object within a TU has practical advantage: it allows for allocating it on the stack (or using some other form of fast allocation, i.e. from a pool of same-sized blocks) while opaque structures leave you with dynamic allocation as the only option (unless you explicitly make sure that they work with your fast allocator). I don't think the aesthetic argument of "it's cleaner that way" is compelling enough.

In your example, you're simply reinventing the mechanism of a virtual call. I could only see it being faster if you group your shapes by type in a single array (which would require sorting it), or have explicit "shape groups" tagged by corresponding shape types that have arrays of shapes only of that type, i.e.:

typedef struct {
/* whatever */
} shape;

typedef struct {
 shape_type type;
 shape *shapes;
 size_t num_shapes;
} shape_group;

typedef struct {
 shape_group *groups;
 size_t num_groups;
} aggregate_shape;

which would require carefully constructing the aggregate shape. Whether the possible speedup is worth the ugly interface is not obvious to me at all.

With all that being said, I could still live in a world with no virtual methods or even classes, where "t->x()" is just syntactic sugar for "x(t)".
But C takes away deterministic object lifetime and templates, replacing them with the pleasure of debugging resource leaks and macro shitsauce, and I don't like that.

To sum up my position on the matter, it is better to evolve from where C++ is, either by adding to the language (short-term) or making a new one (long-term), rather than regressing back into C.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment