Remix.run Logo
mwkaufma a day ago

TL;DR

- no exceptions

- no recursion

- no malloc()/free() in the inner-loop

thefourthchime a day ago | parent | next [-]

I've worked on a playout system for broadcast television. The software has to run for years at a time and not have any leaks, We need to send out one frame of television exactly on time, every time.

It is "C++", but we also follow the same standards. Static memory allocation, no exceptions, no recursion. We don't use templates. We barely use inheritance. It's more like C with classes.

EliRivers a day ago | parent [-]

I worked on the same for many years; same deal - playout system for broadcast, years of uptime, never miss a frame.

The C++ was atrocious. Home-made reference counting that was thread-dangerous, but depending on what kind of object the multi-multi-multi diamond inheritance would use, sometimes it would increment, sometimes it wouldn't. Entire objects made out of weird inheritance chains. Even the naming system was crazy; "pencilFactory" wasn't a factory for making pencils, it was anything that was made by the factory for pencils. Inheritance rather than composition was very clearly the model; if some other object had function you needed, you would inherit from that also. Which led to some object inheriting from the same class a half-dozen times in all.

The multi-inheritance system given weird control by objects on creation defining what kind of objects (from the set of all kinds that they actually were) they could be cast to via a special function, but any time someone wanted one that wasn't on that list they'd just cast to it using C++ anyway. You had to cast, because the functions were all deliberately private - to force you to cast. But not how C++ would expect you to cast, oh no!

Crazy, home made containers that were like Win32 opaque objects; you'd just get a void pointer to the object you wanted, and to get the next one pass that void pointer back in. Obviously trying to copy MS COM with IUnknown and other such home made QueryInterface nonsense, in effect creating their own inheritance system on top of C++.

What I really learned is that it's possible to create systems that maintain years of uptime and keep their frame accuracy even with the most atrocious, utterly insane architecture decisions that make it so clear the original architect was thinking in C the whole time and using C++ to build his own terrible implementation of C++, and THAT'S what he wrote it all in.

Gosh, this was a fun walk down memory lane.

uecker a day ago | parent | next [-]

A multi-inhertiance system is certainly not something somebody who "was thinking in C" would ever come up with. This sounds more like a true C++ mess.

throwaway2037 a day ago | parent [-]

I worked on a pure C system early in my career. They implemented multiple inheritance (a bit like Perl/Python MRO style) in pure C. It was nuts, but they didn't abuse it, so it worked OK.

Also, serious question: Are they any GUI toolkits that do not use multiple inheritance? Even Java Swing uses multiple inheritance through interfaces. (I guess DotNet does something similar.) Qt has it all over the place.

aninteger a day ago | parent | next [-]

The best example I can think of is the Win32 controls UI (user32/Create window/RegisterClass) in C. You likely can't read the source code for this but you can see how Wine did it or Wine alternatives (like NetBSD's PEACE runtime, now abandoned).

Actually the only toolkit that I know that sort of copied this style is Nakst's Luigi toolkit (also in C).

Neither really used inheritance and use composition with "message passing" sent to different controls.

uecker 15 hours ago | parent | prev | next [-]

I take this back ;-) People come up with crazy things. Still I would not call this "C thinking". Building object-oriented code in C is common though and works nicely.

nottorp a day ago | parent | prev | next [-]

One could say toolkits done in C++ use multiple inheritance because C++ doesn't have interfaces though.

WD-42 a day ago | parent | prev [-]

GTK does not support multiple inheritance afaik.

aninteger 21 hours ago | parent [-]

It doesn't but it definitely "implements" a single inheritance tree (with up casting/down casting) which I believe Xt toolkits (like Motif) also did.

webdevver a day ago | parent | prev [-]

it is also interesting that places where you would expect to have quite 'switched-on' software development practices tend to be the opposite - and the much-maligned 'codemonkeys' at 'big tech' infact tend to be pretty damn good.

it was painful for me to accept that the most elite programmers i have ever encountered were the ones working in high frequency trading, finance, and mass-producers of 'slop' (adtech, etc.)

i still ache to work in embedded fields, in 8kB constrained environment to write perfectly correct code without a cycle wasted, but i know from (others) experience that embedded software tends to have the worst software developers and software development practices of them all.

krashidov a day ago | parent | prev | next [-]

Has anyone else here banned exceptions (for the most part) in less critical settings (like a web app)?

I feel like that's the way to go since you don't obscure control flow. I have also been considered adding assertions like TigerBeetle does

https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TI...

tonfa a day ago | parent | next [-]

Google style bans them: https://google.github.io/styleguide/cppguide.html#Exceptions

fweimer a day ago | parent | prev | next [-]

Most large open-source projects ban exceptions, often because the project was originally converted from C and is just not compatible with non-local control flow. Or the project originated within an organization which has tons of C++ code that is not exception-safe and is expected to integrate with that.

Some large commercial software systems use C++ exceptions, though.

Until recently, pretty much all implementations seemed to have a global mutex on the throw path. With higher and higher core counts, the affordable throw rate in a process was getting surprisingly slow. But the lock is gone in GCC/libstdc++ with glibc. Hopefully the other implementations follow, so that we don't end up with yet another error handling scheme for C++.

mwkaufma a day ago | parent | prev [-]

Lots of games, and notably the Unreal Engine, compile without exceptions. EASTL back in the day was in part written to avoid the poor no-exception support in Dinkumware STL and STLport.

jesse__ a day ago | parent [-]

Basically all high profile engine teams I know of ban exceptions. They're worse than useless

Taniwha a day ago | parent | prev | next [-]

yup, same for any real time code, new/malloc/free/delete use hidden mutexes and can cause priority inversion as a result - heisenbugs, that audio/video dropout that happens rarely and you can't quite catch - best to code to avoid them

AnimalMuppet a day ago | parent [-]

They also can simply fail, if you are out of memory or your heap is hopelessly fragmented. And they take an unpredictable amount of time. That's very bad if you're trying to prove that you satisfy the worst-case timing requirement.

pton_xd a day ago | parent | prev | next [-]

That's standard in the games industry as well. Plus many others like no rtti, no huge dependencies like boost, no smart pointers, generally avoid ctors / dtors, etc.

wiseowise a day ago | parent | prev | next [-]

That’s hardly 90% of C++.

elteto a day ago | parent | next [-]

If you compile with -fno-exceptions you just lost almost all of the STL.

You can compile with exceptions enabled, use the STL, but strictly enforce no allocations after initialization. It depends on how strict is the spec you are trying to hit.

vodou a day ago | parent | next [-]

Not my experience. I work with a -fno-exceptions codebase. Still quite a lot of std left. (Exceptions come with a surprisingly hefty binary size cost.)

theICEBeardk a day ago | parent | next [-]

Apparently according to some ACCU and CPPCon talks by Khalil Estel this can be largely mitigated even in embedded lowering the size cost by orders of magnitude.

vodou a day ago | parent | next [-]

Need to check it out. I guess you mean these:

- C++ Exceptions Reduce Firmware Code Size, ACCU [1]

- C++ Exceptions for Smaller Firmware, CppCon [2]

[1]: https://www.youtube.com/watch?v=BGmzMuSDt-Y

[2]: https://www.youtube.com/watch?v=bY2FlayomlE

Espressosaurus a day ago | parent | prev [-]

Yeah. I unfortunately moved to an APU where code size isn't an issue so I never got the chance to see how well that analysis translated to the work I do.

Provocative talk though, it upends one of the pillars of deeply embedded programming, at least from a size perspective.

elteto a day ago | parent | prev [-]

Not exactly sure what your experience is, but if you work with in an -fno-exceptions codebase then you know that STL containers are not usable in that regime (with the exception of std::tuple it seems, see freestanding comment below). I would argue that the majority of use cases of the STL is for its containers.

So, what exact parts of the STL do you use in your code base? Most be mostly compile time stuff (types, type trait, etc).

alchemio a day ago | parent [-]

You can use std containers in a no-exceptions environment. Just know that if an error occurs the program will terminate.

WD-42 a day ago | parent | next [-]

We've banned exceptions! If any occur, we just don't catch them.

elteto a day ago | parent | prev [-]

So you can’t use them then.

_flux 19 hours ago | parent [-]

I don't think it would be typical to depend on exception handling when dealing with boundary conditions with C++ containers.

I mean .at is great and all, but it's really for the benefit of eliminating undefined behavior and if the program just terminates then you've achieved this. I've seen decoders that just catch the std::out_of_range or even std::exception to handle the remaining bugs in the logic, though.

theICEBeardk a day ago | parent | prev [-]

Are you aware of the Freestanding definition of STL? See here: https://en.cppreference.com/w/cpp/freestanding.html Large and useful parts of it are available if you run with a newer c++ standard.

elteto a day ago | parent [-]

Well, it's mostly type definitions and compiler stuff, like type_traits. Although I'm pleasantly surprised that std::tuple is fully supported. It looks like C++26 will bring in a lot more support for freestanding stuff.

No algorithms or containers, which to me is probably 90% of what is most heavily used of the STL.

bluGill a day ago | parent | prev [-]

Large parts of the standard library malloc/free.

gmueckl a day ago | parent | next [-]

But you won't miss those parts much if all your memory is statically initialized at boot.

canyp a day ago | parent | prev [-]

Or throw.

jandrewrogers a day ago | parent | prev | next [-]

i.e. standard practice for every C++ code base I've ever worked on

DashAnimal a day ago | parent [-]

What industry do you work in? Modern RAII practices are pretty prevalent

Cyan488 a day ago | parent | next [-]

This is common in embedded systems, where there is limited memory and no OS to run garbage collection.

criddell a day ago | parent [-]

Garbage collection in C++?

jandrewrogers a day ago | parent | prev [-]

What does RAII have to do with any of the above?

WD-42 a day ago | parent | next [-]

0 allocations after the program initializes.

tialaramex a day ago | parent | next [-]

RAII doesn't imply allocating.

My guess is that you're assuming all user defined types, and maybe even all non-trivial built-in types too, are boxed, meaning they're allocated on the heap when we create them.

That's not the case in C++ (the language in question here) and it's rarely the case in other modern languages because it has terrible performance qualities.

Gupie a day ago | parent | prev | next [-]

Open a file in the constructor, close it in the destructor. RAII with 0 allocations.

dh2022 a day ago | parent [-]

std::vector<int> allocated and freed on the stack will allocate an array for its int’s on the heap…

Gupie 10 hours ago | parent | next [-]

Sure, but my point was that RAII doesn't need to involve the heap. Another example would be acquiring abd releasing a mutex.

usefulcat 19 hours ago | parent | prev [-]

I've heard that MSVC does (did?) that, but if so that's an MSVC problem. gcc and clang don't do that.

https://godbolt.org/z/nasoWeq5M

menaerus 17 hours ago | parent [-]

WDYM? Vector is an abstraction over dynamically sized arrays so sure it does use heap to store its elements.

aw1621107 7 hours ago | parent [-]

I think usefulcat interpreted "std::vector<int> allocated and freed on the stack" as creating a default std::vector<int> and then destroying it without pushing elements to it. That's what their godbolt link shows, at least, though to be fair MSVC seems to match the described GCC/Clang behavior these days.

nicoburns a day ago | parent | prev | next [-]

RAII doesn't necessarily require allocation?

jjmarr a day ago | parent | prev [-]

Stack "allocations" are basically free.

grougnax 16 hours ago | parent [-]

No. And they're unsafe. Avoid them at all costs.

DashAnimal a day ago | parent | prev | next [-]

Well if you're using the standard library then you're not really paying attention to allocations and deallocations for one. For instance, the use of std::string. So I guess I'm wondering if you work in an industry that avoids std?

jandrewrogers a day ago | parent [-]

I work in high-scale data infrastructure. It is common practice to do no memory allocation after bootstrap. Much of the standard library is still available despite this, though there are other reasons to not use the standard containers. For example, it is common to need containers that can be paged to storage across process boundaries.

C++ is designed to make this pretty easy.

nmhancoc a day ago | parent | prev | next [-]

Not an expert but I’m pretty sure no exceptions means you can’t use significant parts of std algorithm or the std containers.

And if you’re using pooling I think RAII gets significantly trickier to do.

theICEBeardk a day ago | parent [-]

https://en.cppreference.com/w/cpp/freestanding.html to see the parts you can use.

astrobe_ a day ago | parent | prev [-]

And what does "modern" has to do with it anyway.

tialaramex a day ago | parent | prev | next [-]

Forbidding recursion is pretty annoying. One of the nice things that's on the distant horizon for Rust is an explicit tail recursion operator perhaps named `become`. Unlike naive recursion, which as this video (I haven't followed the link but I'm assuming it is Laurie's recent video) explains risks stack overflow, optimized tail recursion doesn't grow the stack.

The idea of `become` is to signal "I believe this can be tail recursive" and then the compiler is either going to agree and deliver the optimized machine code, or disagree and your program won't compile, so in neither case have you introduced a stack overflow.

Rust's Drop mechanism throws a small spanner into this, in principle if every function foo makes a Goose, and then in most cases calls foo again, we shouldn't Drop each Goose until the functions return, which is too late, that's now our tail instead of the call. So the `become` feature AIUI will spot this, and Drop that Goose early (or refuse to compile) to support the optimization.

tgv a day ago | parent | next [-]

In C, tail recursion is a fairly simple rewrite. I can't think of any complications.

But ... that rewrite can increase the cyclomatic complexity of the code on which they have some hard limits, so perhaps that's why it isn't allowed? And the stack overflow, of course.

AnimalMuppet a day ago | parent [-]

I don't know that it's just cyclomatic complexity. I think it at least part of it is proving that you meet hard real-time constraints. Recursion is harder to analyze that way than "for (i = 0; i < 16; i++) ... " is.

morshu9001 12 hours ago | parent | prev | next [-]

Thinking recursively is one thing, but can't remember the last time I've wanted to use recursion in real code.

zozbot234 a day ago | parent | prev [-]

The tail recursion operator is a nice idea, but the extra `become` keyword is annoying. I think the syntax should be `return as`: it uses existing keywords, is unambiguous and starts with `return` which tail recursion is a special case of.

tialaramex a day ago | parent [-]

Traditionally the time for bike shedding the exact syntax is much closer to stabilization.

Because Rust is allowed (at this sort of distance in time) to reserve new keywords via editions, it's not a problem to invent more, so I generally do prefer new keywords over re-using existing words but I'm sure I'd be interested in reading the pros and cons.

zozbot234 a day ago | parent [-]

The usual argument against a decorated `return` keyword is that a proper tail call is not a true "return" since it has to first drop any locals that aren't passed thru to the tail call. I don't think it's a very good argument because if the distinction of where exactly those implicit drops occur was that important, we'd probably choose to require explicit drops anyway.

petermcneeley a day ago | parent | prev | next [-]

This is basically video games prior to 2010

mwkaufma a day ago | parent [-]

Relax the dynamic-memory restriction to "limit per-event memory allocation to the bump allocator" and it's still mostly true for many AAA/AAAA games I work on today.

petermcneeley a day ago | parent [-]

Developers have gotten lazy. Im glad to here where you are they are at least still trying.

mwkaufma a day ago | parent [-]

Nah I'm lazy, too.

petermcneeley a day ago | parent [-]

but_you_were_the_chosen_one.jpeg

msla a day ago | parent | prev [-]

At that point, why not write in C? Do they think it's C/C++ and not understand the difference?

> no recursion

Does this actually mean no recursion or does it just mean to limit stack use? Because processing a tree, for example, is recursive even if you use an array, for example, instead of the stack to keep track of your progress. The real trick is limiting memory consumption, which requires limiting input size.

billforsternz a day ago | parent | next [-]

Semi serious idea: A lot of people (including me) write C++ but it's basically C plus a small set of really ergonomic and useful C++ features (eg references). This should be standardised as a new language called C+

zeroc8 15 hours ago | parent [-]

That probably would see more success than the monster they've created. I've been out of the C++ world for a while, but I hardly recognize the language anymore.

mwkaufma a day ago | parent | prev | next [-]

For a long time, at least in MS and Intel, the C++ compilers were better than the C compilers.

drnick1 a day ago | parent | prev | next [-]

You may still want to use classes (where they make sense), references (cleaner syntax than pointers), operator overloading, etc. For example, a linear algebra library is far nicer to write and use in C++.

jesse__ a day ago | parent [-]

Function overloading is nice, too

mwkaufma a day ago | parent | prev [-]

Re: recursion. She explains in her video. Per requirements, the stack capacity has to be statically verifiable, and not dependent on runtime input.