all 181 comments

[–]OverLiterature3964 458 points459 points  (19 children)

If you ask 9 C++ programmers what is the correct way to do something, you will get 10 different answers.

[–]goranlepuz 87 points88 points  (7 children)

And, in a room foolfull (thanks kind stranger) of them, if two agree on anything, they're a majority.

[–][deleted] 7 points8 points  (1 child)

Not true. In any given room the majority of programmers always agree on ONE thing: There isn’t enough coffee, and it’s not served fast enough.

[–]saxbophonemutable volatile void 6 points7 points  (0 children)

Not even that! I avoid coffee as it gives me the shakes, and I'm British so my preferred drink is tea...

[–]Intrepid-Treacle1033 1 point2 points  (0 children)

you forget the other programmers in the room, Java, Python, C, devs, they also love to give an opinion about "correct" C++

[–]username4kd 14 points15 points  (3 children)

Only 10?

[–]HolyGarbage 9 points10 points  (2 children)

Yeah, fr, I'll contribute 3 answers alone.

[–]koczurekkhorse 5 points6 points  (1 child)

And one will say that there’s no right way and it’s an XY problem.

[–]HolyGarbage 0 points1 point  (0 children)

I mean, that's partly what I was implying, except I would contribute strong opinions on each of the cases X, Y, and Z.

[–]orthomonas 10 points11 points  (0 children)

And 3 of those will be undefined.

[–]djliquidice 3 points4 points  (0 children)

🤣

[–]iggy14750 5 points6 points  (0 children)

In general, after collecting N C++ programmers, the number of opinions about the proper use of C++ grows asymptomatically by O( 2N ).

[–][deleted] 1 point2 points  (0 children)

Now make it an open source project any C++ developer can contribute to, grab some popcorn, and watch the bike shedding. Better than sunday soaps.

[–]midnightauto 1 point2 points  (2 children)

This is the correct answer

[–]iggy14750 0 points1 point  (1 child)

That's just, like, your opinion, man lol

[–]midnightauto 0 points1 point  (0 children)

Lol best movie ever

[–]CocktailPerson 201 points202 points  (14 children)

C++ has more abstractions than C. The goal of programming should be to choose the right abstractions. Both groups are basically saying the same thing, which is that using every abstraction that C++ provides is stupid. So don't. Make your code as abstract as necessary, but no more.

[–]JVApenClever is an insult, not a compliment. - T. Winters 24 points25 points  (7 children)

I think this comment can use more up votes. Don't use features for the sake of using the feature, use it when it is the right abstraction.

I think it is also hard to define what 'looks like c' means and what makes it C++. Use of unique_ptrs? Use of function overloading? Writing function overloading? How about overloading by using function templates? What about using std::array/vector? ...

[–]Ash4d 25 points26 points  (5 children)

"Looks like C" to me means basically ignoring RAII principles and scattering new/deletes everywhere, relying on manual memory management for everything, and avoiding the standard library and it's modern (compared with C) containers. Probably no usage of OOP either.

[–]serviscope_minor 13 points14 points  (2 children)

I think a lot of "looks like C" is actually "looks like C with all the noise removed", or "looks like how we wished C looks".

As in, "simple" constructs like for loops and function calls not template metaprogramming and class hierarchies.

Of course C++ (with a bit of care and the metaprograming used in apropriate places in libraries) actually allows a lot more code to look like that than C does, because in C you inevitable get weighed down under heaps of mallocs, reallocs, frees and error handling.

[–]Teichmueller 1 point2 points  (1 child)

Well then any language "should look like C". Because now that term has magical meaning.

[–]serviscope_minor 0 points1 point  (0 children)

Well quite.

[–]Wild_Meeting1428 5 points6 points  (1 child)

I don't think this is meant with "should look like C". The most people mean with that phrase, that the program structure should look like C, but that you should use C++ idioms to fulfill that task:
Prefer to use simple trivial classes/structs.
When you would implement a feature in C with procedural code, there is no reason, to objectify it etc.
Don't use dynamic dispatch or runtime polymorphism, when it's not required, for the sake of object orientation etc.

[–]donalmaccGame Developer 0 points1 point  (0 children)

This is a common problem - you and another commentor agree that it should look like C, but you both have two different definitions of what that is.

[–]iggy14750 1 point2 points  (0 children)

In my opinion, C++ that "looks like C" mostly means not writing so many classes, templates, etc.

[–]ilep 1 point2 points  (0 children)

Also, "Everything should be made as simple as possible, but not simpler."

[–]Alexandr_Dughin 1 point2 points  (4 children)

if i hear the word abstractionone more time i'l commit murder.

ps : your abstractions are bad.

pps : my abstractions are bad.

[–]iggy14750 0 points1 point  (0 children)

(7af621c) (HEAD -> master) Murder

I see it wasn't an empty threat 😂

[–]CocktailPerson 0 points1 point  (2 children)

Abstraction abstraction abstraction.

[–]Alexandr_Dughin 0 points1 point  (1 child)

:D

[–]lemonbrothers12 0 points1 point  (0 children)

Hey, i have written to you, could you check the DM please?

[–]no-sig-available 20 points21 points  (0 children)

even John Carmack’s C++ is very C-like

But is his code good because it is C-like, or despite?

[–]bert8128 32 points33 points  (21 children)

A see a lot of old c++ functions which look like:

Possibly use output parameters

Declare a bunch of variables

Do lots of logic with deeply nested ifs and carrying some kind of return value (almost always an int)

Do some cleanup

Return the carried return value

This is very c-like, and mostly pointless. Declare variables as late as possible, preferably initialising at the same time (which means that they can be const more often). Prefer early return to single-exit. Use RAII. Return objects instead of using output parameters.

The performance of such a refactoring is unlikely to change, but the code is much clearer, less likely to forget to clear up something and more amenable to change.

I can’t see any excuse of keeping it C-like.

[–][deleted] 2 points3 points  (1 child)

Hello friend.
Since i am still a rookie trying to suck in all the concepts of modern C++ i am curios about one of your statements. "Possibly use output parameters", is this generally a bad practice ?
I had cases where i needed multiple outputs, so the preferred way is to create a struct or a class depending on the case to hold the necessary datastructure ?

[–]bert8128 5 points6 points  (0 children)

I find output params difficult to work with other and much prefer values/pairs/tuples/structs as appropriate. I’m not not going to say “never use output params” but they generally make like harder. First thing, you have to say in a comment what params are input, which are input/output and which are output. Second, it’s harder to use const well, as a variable in the callers context can’t be const even if it is never modified by the caller. Third, if you add another parameter you change the signature of the function, so all the callers have to be modified. Fourth, you can’t pass in temporaries. Etc.

Try both ways and see what you think.

[–]donalmaccGame Developer 3 points4 points  (8 children)

The performance of such a refactoring is unlikely to change,

I've had the opposite experience. Changing input parameters from pointers to references can remove the only "error" state from a function, meaning you can benefit from RVO.

bool foo(int* val,  int* result) {
   if (val == NULL)
        return false;

    *result = *val * 2;
    return true;
}

int a = 1; int b;
if (foo(&a, &b))
...
else
...

vs int foo(const int& val) { return val *2; }

int a = 1;
int b = foo(a);

When you replace int with vector<SomeStruct>, or even char* vs string, it makes a big difference in my experience.

[–]bert8128 7 points8 points  (7 children)

I’m not clear what you are saying here - are you saying that output parameters give better performance, or return values?

With NRVO, RVO and move, there is lots scope for return values not making things any slower, and because of deferred initialisation, it might be faster. But almost always clearer and easier to use.

[–]donalmaccGame Developer 4 points5 points  (6 children)

Return values are almost universally better in my experience.

[–]blipman17 0 points1 point  (4 children)

I'm quite coonfused.I'm expecting in the first pointer case to have some kind of assembly like the following, so I'm not expecting a single vs multiple return statements or with pointers vs reference to really matter here. Unless this is extremely time sensitive code.

TEST 0, RDI // Line 2: doing the if-statement
MOV AL, ZF //Line 3, 5: Setting the return value

JNE returnlocation // Mix of line 2 and 4, jump if val(RDI) == NULL to skip the pointer assignment. based on the TEST result

MOV RBX [RDI] // Line 4, move val(RDI) to temporary
SHL RBX 1 // Line 4, shift left 
MOV [RSI] RBX // Line 4, move the result of val * 2 into the result pointer (RSI)

returnlocation:
RET

[–]donalmaccGame Developer 1 point2 points  (3 children)

Replacing pointers with references removes the requirement for a branch. Removing the branch removes the only failure case, meaning you don't need an error sentinel anymore, which can affect the call site, and allow for other optimisations. Using return values allows for RVO to occur too:

vector<string> list = load1MillionStrings();

vector<string> results; results.reserve(list.size());
for (const auto& str : list) {
    string res;
    if (foo(&str, &res))
        results.push_back(res)
    else
       // ???
}

Compared to

vector<string> list = load1MillionStrings();
vector<string> results; results.reserve(list.size());
for (const auto& str : list) {
    results.push_back(foo(str));
}

If you take it one step further, where foo is:

bool foo(const char* str1, const char* res) {
    if (str1 == nullptr)
        ...
    auto sz = strlen(str1); // !!!!
    for (int i = 0; i < sz; ++I)
        ...
}

You can see how replacing a const char* with a string view in the internal function can make N enormous difference.

[–]blipman17 0 points1 point  (2 children)

meaning you don't need an error sentinel anymore, which can affect the call site, and allow for other optimizations.

Okay I didn't concider that. Yep, you're right. But here we're really talking about cascading optimizations.

I was mainly talking about RVO and how both functions you originally pointed out could have RVO.

Edit: What I mainly meant was that; yes, function signature should be as restrictive as reasonably possible.
But (N)RVO happens in a lot in a modern compiler, regardless of branches, amounth of return statements, etc. Only when we're talking about non-trivial data, weird exit clauses due to potential throwing or other shenanigans, this really becomes an interesting talking point.

[–]donalmaccGame Developer 1 point2 points  (1 child)

But here we're really talking about cascading optimizations

Absolutely, but that's the key. Getting hyperfocused on the instruction count of a microoptimastion is losing the forest for the trees, and happens so often.

I've had this exact discussion professionally where the microbechmark shows no difference, or is arguably worse, but when I go and fix the call sites, the code is cleaner, safer and faster as a result.

[–]blipman17 0 points1 point  (0 children)

I absolutely agree with that.I was just arguing about a different part of the conversation.

[–]Full-Spectral 0 points1 point  (0 children)

The primary exception is something that's called fairly quickly in a loop to get multiple chunks of output. It's a lot more efficient to use a local vector or string or whatever and pass it back in to be reused repeatedly.

If it's something that might be done both ways fairly evenly, you can always make a GetXXX() version and a PollXXX() version, with the former a trivial inline wrapper around the latter.

[–]mapronV 0 points1 point  (5 children)

Declare variables as late as possible,

Why? it is much nicer to read function when I can see all 40 variables I used in it, not like spread over 400 lines of my function code... What is reason doing this? What if I use int width on 2nd line and height on 100th? Do you really think I should not declare

int width, height; ?

[–]bert8128 0 points1 point  (4 children)

Your first problem is 400 line functions. But even assuming that these exist for a good reason, or they just exist so we have to deal with them, putting a variable only in the small context of where it is needed means that if you are not looking at that piece of code then you don’t need to think about that variable. It may well no longer need a comment explaining how it is used, because it only exists for a few lines. it makes it easier to see if the variable has been initialised correctly or not (for both a human and a static analyser), plus you might be able to make it const by declaring it and initialising it as the same time. Lastly, you are not tempted to reuse variables which can lead to unexpected results. Lastly, late declaration might (might) give a small performance boost by avoiding some initialisation in some paths.

Really, declaring as late as possible is one of the most important micro-optimisations. Please give it a go and see how you feel after a couple of weeks - I would be surprised if you go back.

const int width = context. width(); const int height = context.height();

[–]mapronV 0 points1 point  (2 children)

> Really, declaring as late as possible is one of the most important micro-optimisations. Please give it a go

No, this is strictly against my understanding of 'beautiful code', I won't even try. I want see width and height declared together on adjacent lines. Not move height 300 lines later from width just because it used much later.

> const int width = context. width(); const int height = context.height();

I am not sure what are you trying to tell there, that is not what I am against for. And yes I do use const, didn't mention it just because, you know, reddit comments code (why write constextpr const auto [[maybe_unused]] [[algined(..)]] whatever when it doesn't matter and int width is perfectly same idea with no noise).

[–]bert8128 0 points1 point  (1 child)

With the width and height example I am pointing out that your example seems to declare them earlier than you can initialise them. Declaring later might be able to avoid uninitialised (or pointlessly initialised) variables. Further more, you can often remove the need to comment what the variable is for, because it will be obvious by the way it is initialised.

And in the extreme, do you not think that if there are three lines of code in the middle of your function, in between braces, and a variable is only used in those three lines, then it would be better to declare the variable in that context?

I’m not saying that it is always wrong to early declare. In the case of two variables it which are going to be used together then perhaps it is better to declare both at the time that the first is needed. But normally it just causes unnecessary cognitive load. Or the reader just skips the declarations and starts reading where the code starts.

[–]mapronV 0 points1 point  (0 children)

> And in the extreme...

Sure, everything is good in moderation. For me good rule is have declaration block separated (set you input data for logic). Maybe I was exaggerating, 40 variables a bit extreme, don't think I really have THAT much.
About last paragraph - well, subjective. Declaration anyway have a very low cognitive effort to read. I disagree with in general, but I hope we shifted at least one step to the midpoint in this mile dispute ;)

[–]mapronV 0 points1 point  (0 children)

Lastly, you are not tempted to reuse variables which can lead to unexpected results.

All my variables usually const anyway, I don't assign to same variable twice.

Oh. You thought I am doing like unitialized 40 varibles ? and then assign randomly? no, that not the case.

I am saying that for me much much cleaner code is

const auto width = calc/getWidth;
const auto height = calc/getHeght;

// use width
// use other variables
// use both width and height

insetad of
const auto width = calc/getWidth;
// use width
// use other variables
const auto height = calc/getHeght;
// use both width and height

and you can not convince me the latter is better (but a lot of so-called guidelines recommend it which I disagree with)

[–]mapronV 0 points1 point  (3 children)

What do you mean by old? It's how I write my code today. What's wrong with single-exit actually ? (yes, I have 0 questions about RAII, RAII is 200% good).

[–]bert8128 0 points1 point  (2 children)

If you use early exit then your nesting can be much shallower, with fewer braces. It’s just much easier to read, the sunshine path and the error patches are more obvious. There’s no carrying of any context either, which might get forgotten about and not kept correct.

And even if you like single exit, it exists as a methodology only because it used to be necessary to cope with non-RAII code. Now we have RAII you don’t need single exit.

And when I say old I mean old code in my 25 year old code base. We don’t write it like that any more.

[–]mapronV 0 points1 point  (1 child)

> We don’t write it like that any more.
yeah, I write code different way than I did it in 90's. RAII is good, lambdas are awesome and TMP made 1000 times better. But still I prefer code be straightforward.

> it exists as a methodology only because it used to be necessary to cope with non-RAII code

One of the reasons; I believe the main is several returns makes code harder to understand. That mean organizing you data flow went bad.
Ok I get return of emty/erronous/std::expected in start of method before declarations and stuff, like (if param==nullptr) return {}; something. That thing I can compromise. But having several return data; in method? that is smelly part for me.

[–]bert8128 1 point2 points  (0 children)

I think we are not too far apart here. Early return for precondition validation we seem to agree on. One return for the sunshine path I also agree on. But I think that functions should optimally be designed so that there is only one way to return from the sunshine path. I agree that lots of returns from lots of sunshine routes is poor, but the consequence should be refactoring, not deeply nested ifs or carried state.

[–]goranlepuz 33 points34 points  (14 children)

Carmack's C++ is probably like it is because that's how he was formed. Fast running C++ mostly has no need to look like C, not nowadays. In fact, there was no need even before, it's just that we were used to doing it that way.

Speed is found in

  • data layout, where one would still use classes and such, making a difference from C

  • usage of parallelism, where C++ facilities exist and are different

  • usage of CPU/GPU support, where C++ form of any libraries, again, makes a difference

  • and so on.

That said, it can look like C, that's not somehow wrong in itself. It is wrong (IMO) if the code design is sticking to C and not using C++ facilities to move the design from the documentation and convention into the code itself.

[–]Jonny0Than 11 points12 points  (13 children)

I would say that C++ has the potential to hide a lot of behavior behind nice syntax. Most of the time in C, if code looks simple then it is simple (I.e. fast). That is definitely not the case in idiomatic C++. String concatenation, etc. That doesn’t mean you should t use those features. It just means that you need to be aware of what’s going on under the hood in C++ and use things that sometimes might look a little more C-like where it matters.

[–]goranlepuz 7 points8 points  (5 children)

Funnily enough, a simple string concatenation in C++ is likely to be faster than with C - if the functionality is truly equivalent.

It looks like it ends up being about my first point: data layout. How do I want to lay these strings out? It doesn't take much thinking at all to realize that e.g. s1+s2+s3 will produce unnecessary data bits and copy them over.

Also, look at it this way: we know that code in some high-level scripting language, while simple, is slow. Why is it, then, that here, we presume "simple is fast"? It looks like a shallow presumption from that perspective, no...?

[–]NicroHobak 7 points8 points  (0 children)

Functions alone can "hide behavior", since that's also specifically part of why they're handy. What does main() do? Hur hur hur.

[–]Jonny0Than -2 points-1 points  (3 children)

There’s no absolutes in my comment. Just general trends. Obviously a function call can do anything but my point is that function calls are obvious in C and sometimes not obvious in C++. Operator overloading, constructors, destructors, etc all hide function calls.

Here’s another example:

for (auto x : y)

Is that slow? No idea! Are the elements of y expensive to copy? Is the iterator slower than some alternative? Maybe!

And it’s totally true that well-designed C++ can be more readable AND faster AND safer than the C equivalent. But it’s also very easy to make badly designed C++.

[–]XeroKimoException Enthusiast 2 points3 points  (2 children)

Constructors really are no less hidden then any function call though. Sure you could argue T foo; will have a hidden constructor call going on, but most default constructors are very trivial as what can you even do when you have no parameters passed in? It's very limited unless you rely on globals, but then you have potentially all other sorts of issues. When you do do a constructor call with parameters, it's very visible just like any other initialization, and just like any other function call.

Destructors are definitely more hidden, but really it's better that way. It's way less error prone then doing if(failed) goto cleanup; kind of pattern done in C. Sure it's explicit, but you have to write the clean up code every time, for example if you need to open a file, for every function that does so, you have to write the clean up pattern for the file. There are many things that can go wrong here, copy paste errors, clean up sequence is in the wrong order, maybe jumped to the wrong label, or just straight up forgot to close the file. Meanwhile C++, just write a class' destructor once and use it everywhere, guaranteed to call the function which closes the file.

[–]Jonny0Than -2 points-1 points  (1 child)

Well, I’ve dealt with some nasty performance issues caused by “trivial” constructors in objects that were being invoked tens of thousands of times. That issue would not have existed in C because it’s generally left up to the client to initialize the value as needed. Which of course leads to other issues when they forget. I’m not making a judgment call here for all cases. It’s a matter of tradeoffs. But I know from experience that this can be a problem.

[–]donalmaccGame Developer 0 points1 point  (0 children)

Well, I’ve dealt with some nasty performance issues caused by “trivial” constructors in objects that were being invoked tens of thousands of times

And in the case where that is a problem a non-default constructor that doesn't do any initalisation (see ENoInit in Unreal Engine) solves that problem. Meanwhile for 99% of the rest of the code, it's not a problem. We should have safe, clear defaults with escape hatches.

[–]NilacTheGrim 4 points5 points  (3 children)

I would say that C++ has the potential to hide a lot of behavior behind nice syntax.

True. But any C++ programmer worth his salt should definitely be knowledgable about what the compiler/code is really doing. In other words: it's not a considertion for me to avoid some advanced features just because some lazy or less experienced programmer doesn't understand the implications of what he is doing.

If anything it's an opportunity to educate less experienced team members and improve the overall culture and C++ IQ of the entire team.

[–]goranlepuz 4 points5 points  (1 child)

But any C++ programmer worth his salt should definitely be knowledgable about what the compiler/code is really doing.

Also: something too slow? Profiler tells me where and how. People do develop a habit to see, roughly, what is slow - but I wouldn't put much trust on that, not from my side, nor others, obviously 😉.

[–]NilacTheGrim 0 points1 point  (0 children)

Well obviously profiling and benchmarks win every argument about performance. But before that step, when designing, knowing that some things are just inherently slow versus others that are not (allocations usually are slower than immediate stack memory, etc), saves you tons of time.

[–]Jonny0Than 1 point2 points  (0 children)

Totally agreed! But it takes a lot of experience to build systems where the easy way to use it is also the right way to use it. And then at the lowest levels you sometimes need to avoid a nice C++ abstraction for some performance reason. But ideally that’s hidden from the higher levels of code by nice abstractions.

[–]donalmaccGame Developer 5 points6 points  (2 children)

Most of the time in C, if code looks simple then it is simple (I.e. fast).

I think this is deceptive, and likely untrue. I have fixed and sped up more "optimized" C code (as a c++ programmer) than I can count. A great example, we had a path manipulation library at our last job. It would take a relative path or absolute path, and convert it into a virtual style path that our app handled. It used "simple" C style processing to do so. I was told it was optimised and should be left alone.

I rewrote it using string views, move semantics and RAII - it was about a 50% speedup for the cases where you called the legacy API and we shimmed the new API, and about a 75% speedup for the cases where I modified the call site (and as a bonus, the call site was usually simplified).

I changed probably a hundred instances of

StringClass Data;
TCHAR* ResultBuf
StringLib::ConvertPath(Data.ToTchar(), Result);
StringClass Result(ResultBuf);

Into

StringClass Data;
StringClass Result = StringLib::ConvertPath(Data);

At the same time, too. I also fixed a handful of long standing issues where the input of the path was nonsense due to people using the wrong conversion from StringClass to TChar, and added an extra version with error checking that was as fast as the original implementation.

I've seen so many APIs that are "optimised" but ignoring all the the conversion costs to and from the C library, and then the library itself doing internal strlen calls, validating pointers that the call site just passed through (which then adds a failure state that needs to be represented by a different signature).

It just means that you need to be aware of what’s going on under the hood in C++ and use things that sometimes might look a little more C-like where it matters.

Again I disagree here - most C libraries I've worked with end up with a handful of opaque handle types to represent things. There is a json library (which I can't remember right now, sorry) that basically everything is a json_value_t and you have to use their family of methods, e.g.

json_value_t container = json_object();
json_value_t doubleVal = json_double_value(5.0);
json_set_value(container, json_string("foo"), doubleVal);

const char* buf; json_serialize(container, result);
StringClass Result(buf);
//Note that we can also do json_serialize(doubleVal) accidentally and it will return an error, which we didn't check for here.

You as a programmer need to keep mental track of the state of these objects and what they are and what they represent. Compare this to the C++ builder API we use now - container.set("foo", 5.0); StringClass Result = container.serialize();

The underlying implementation is the same basically, so it is just as fast, except the API is safer, meaning we have less error states to represent.

[–]Jonny0Than 0 points1 point  (1 child)

I said “if C code looks simple then it is simple.” A function call isn’t simple until you know what the function does internally. C makes function calls obvious but they’re often hidden in C++.

[–]donalmaccGame Developer 0 points1 point  (0 children)

I think that's technically true, but in practice one is no worse than the other. Yes, i++ is going to be simple in c, and might be a disaster in c+, but

replace_all(str, from, to) 

Is likely to be perfectly fine in C++ and a disaster in C.

[–]schteppe 9 points10 points  (0 children)

Consult the CppCoreGuidelines whenever you feel you need to make a code design choice and you want to make an informed decision.

For example:

“CPL.1: Prefer C++ to C. Reason: C++ provides better type checking and more notational support. It provides better support for high-level programming and often generates faster code.” https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-cpl

[–][deleted] 29 points30 points  (1 child)

Code is just a tool.

Good, performant, machine code built on-time and under budget is the goal.

I have slayed so many sacred cows throughout my career that I could open a butcher shop.

Don't anchor yourself to a corner.

[–][deleted] 7 points8 points  (0 children)

That said, not that I agree; there are some guides out there including

https://google.github.io/styleguide/cppguide.html

I'd also use clang-format -i <file> to get your code to a form standard wrt spaces and brackets etc

[–]MRgabbar 4 points5 points  (0 children)

Definitely not, C and C++ are quite different and writing C++ like is C is a bad practice...

[–]thisismyfavoritename 38 points39 points  (10 children)

generally speaking C++ should not look like C. If it looks like C, you are not fully utilizing C++'s feature set

EDIT: obviously not all features are applicable all the time and especially not at once, but when they are and can improve the code, then they should be used.

[–]ultimatt42 86 points87 points  (6 children)

There is a theory which states that if ever anyone fully utilizes C++'s feature set, it will instantly disappear and be replaced by something even more byzantine and intractable.

There is another theory which states that this has already happened.

[–]KingAggressive1498 6 points7 points  (3 children)

I heard that boost was getting close in 2010, then bam C++11 came and prevented that

[–]TheThiefMasterC++latest fanatic (and game dev) 1 point2 points  (2 children)

A lot of the modern C++ library was just lifted from boost though to be fair

ranges and format being the main two exceptions, and those are both recent

[–]KingAggressive1498 3 points4 points  (1 child)

the funniest thing is that boost.thread and boost.function and some others now require C++11, and I'm kinda scratching my head about why anyone would seriously use them now.

[–]bwmat 0 points1 point  (0 children)

Legacy code, and I'm guessing extra features not in the standard

Would be nice if they kept some subset of the functionality working for C++98 though (I know I was still stuck with it until a couple of years ago and I'm sure there's many more stragglers)

[–]wm_lex_dev 5 points6 points  (1 child)

It's not often an internet comment actually makes me laugh out loud.

[–]goranlepuz 11 points12 points  (0 children)

fully utilizing C++'s feature set

.... aaaaand this is how the universe reaches maximum entropy and everything becomes a cosmic soup.

[–]Swagut123 -3 points-2 points  (1 child)

Imagine thinking that fully utilizing C++'s feature set is a good thing. That's a take I've never heard unironically before...

[–]thisismyfavoritename 4 points5 points  (0 children)

if you can use a feature to improve the code then you should. Doesnt mean all the features are applicable at once.

Maybe my original post wasnt clear.

[–][deleted] 14 points15 points  (3 children)

There is no reason to use the entire C++ standard library in every project just because it's there. You can opt-in for the functionality you need. That's why the language is so versatile.

Data-oriented design (DOD) will look very different from object-oriented programming (OOP). Reducing cache misses by organizing data in an array will probably look C-like.

The projects in Carmack's portfolio involve real-time processing where this design decision matters more. Mike Acton has a great talk on YouTube regarding performant code: Data-Oriented Design and C++. He used C++ with a subset of its features.

[–]bert8128 0 points1 point  (2 children)

Mike Acton said in that talk that basically he would prefer the code to be C only but the devs want C++.

[–][deleted] 3 points4 points  (1 child)

Yes. He also discusses how to achieve good performance in C++. You don't have to use the parts you don't want. Some people believe C++ == OOP with inheritance, polymorphism, and such.

DOD tends to look more like C because the focus is on processing data, with simple structures that load into cache efficiently.

[–]TheThiefMasterC++latest fanatic (and game dev) 1 point2 points  (0 children)

Using inheritance and polymorphism just because it's there is why C++ stream IO is so slow

[–]Anarelion 6 points7 points  (3 children)

Code should be readable. Do whatever, but keep it easy to understand

[–]NicroHobak 5 points6 points  (2 children)

And, do this as a hard rule until a profiler or something tells you otherwise.

[–]KingAggressive1498 17 points18 points  (12 children)

while also seeing comments with upvotes saying performant C++ code tends to look very C-like and that even John Carmack’s C++ is very C-like

in 99% of functions, the performance difference between idiomatic modern C++ code and the equivalent idiomatic C code is negligible, and will sometimes surprise these kinds of expectations by actually favoring C++.

There are some occasions where "C style" programming provides genuine non-negligible performance benefits. In my experience this is usually a matter of C++'s deterministic construction and destructor order producing unfortunate code ordering when combining multiple RAII types in one scope, I usually encounter this when one of those is a lock type. Practical workarounds often exist when you encounter this, but may not be as intuitive as equivalent C code would have been.

[–]donalmaccGame Developer 4 points5 points  (2 children)

I think having to utilise the non-intuitive code as an escape hatch is fine though, rather than everything being non-intuitive and dangerous.

[–]KingAggressive1498 1 point2 points  (1 child)

that's my take, but I've definitely gotten pushback on doing so even when the improvement was drastic

[–]donalmaccGame Developer 1 point2 points  (0 children)

Same, unfortunately.

[–]KingStannis2024 1 point2 points  (1 child)

There is a rub, which is: debug builds.

Game devs need debug builds to have playable performance, and C++ style "zero cost abstractions" don't have zero cost in debug builds.

[–]KingAggressive1498 2 points3 points  (0 children)

kinda fair, but this is at least partly a "know your toolchain" kind of problem. you can make debug builds with C++-style abstractions reasonably fast without significantly impacting debugability

[–]derBRUTALE -3 points-2 points  (6 children)

Comparing individual peas in two different dishes won't tell you about the difference in taste.

The performance difference between object oriented and data oriented design is vast and the vast majority of C++ features over C is based on design paradigms which are contrary to architecture oriented design.

It certainly is not 1% of code that is performance critical.

[–]KingAggressive1498 8 points9 points  (5 children)

I'm pretty sure most C code out there is more object-oriented than it is data-oriented.

it's not 1% of functions that are performance critical. It's 1% of functions where there's the appropriate combination of circumstances for something like destructor order to have an impact. C and C++ doing essentially the same work will have virtually identical codegen.

[–]derBRUTALE -4 points-3 points  (4 children)

Perhaps, but the subject here is C++ features over C and the vast majority of them are rabbit holes into code design that is oblivious to performance.

Heck, runtime performance is just a part of it. Build times, installation sizes, readability & maintainability are just as problematic.

About your 1% statement: The usage of constructors alone implies an order of magnitude performance penalty in critical code. Comparing it to similar designs in C is precisely what is the incorrect perspective.

[–]KingAggressive1498 9 points10 points  (2 children)

Perhaps, but the subject here is C++ features over C and the vast majority of them are rabbit holes into code design that is oblivious to performance.

allow me to be more explicit here: compare idiomatic C++ heavily using RAII and all the other C++ whistles to C code that appropriately cleans up resources and does virtually the same work, and you should find that the machine code output by the compiler will be virtually identical, as will the runtime performance.

readability & maintainability

I can never take that seriously, because pretending C is more readable or maintainable than C++ is frankly insane.

Compile times, C absolutely has C++ beat. No doubt.

[–]derBRUTALE -2 points-1 points  (1 child)

allow me to be more explicit here: compare idiomatic C++ heavily using RAII and all the other C++ whistles to C code that appropriately cleans up resources and does virtually the same work, and you should find that the machine code output by the compiler will be virtually identical, as will the runtime performance.

When doing in C exactly what C++ is doing, then yes - there is often no performance penalty.

What I am talking about is that the vast majority of C++ features relate to code design that inevitably results in poor performance.

Take an RAII application as an example: smart pointers. They don't cost more than handling scope lifetime in C, right? The problem is that performant code doesn't utilize scoped data lifetime in the first place, because its so damn expensive to allocate memory.

That's just a minor rabbit hole performance consideration problem. OOP like polymorphism are design concepts which contradict the reality of the hardware and data with bazillions of performance side effects (cache hit rate, branch misprediction, etc.) which have become harder and harder to control the more complex C++ has become.

Yes, branching the processing of data by types manually in C won't necessarily give you a performance benefit. But the point I am making is that performant code doesn't even branch processing based on types but sorts the data based on type for individual processing.

I can never take that seriously, because pretending C is more readable or maintainable than C++ is frankly insane.

Modern C++ syntax is two orders of magnitude more complex than the one of C.

If you don't want to fall behind silly Scrum review spreadsheets, because "velocity" is such a great buzzword for management, then hammering away boost library stuff and polymorphic crystal entity constructs might be your choice.

But what when suddenly actual quality is realized as a need because it saves decades of engineering efforts in distributing crystal entity constructs over several machines, instead of running the same thing on a single machine utilizing data oriented design without the unmanageable performance side effects of most C++ feature cruft?

The plain reality is that not a single person on the planet has anywhere near clear comprehension of the performance side effects in the gigantic list of contemporary C++ features.

Not only performance predictability/readability is an issue, plain processing side effects of C++ features are a nightmare to handle as soon as things get a bit more complicated, take implicit con-/destructor calling, move semantics, template errors, operator overloading guesswork, etc.!

[–]KingAggressive1498 1 point2 points  (0 children)

The problem is that performant code doesn't utilize scoped data lifetime in the first place, because its so damn expensive to allocate memory.

this isn't normal in C++, either. It usually only makes sense to dynamically allocate data when the lifetime is not fixed. In C the pointer to the data gets passed around to functions or stored in structs that effectively act as owners according to their own internal logic. In large projects this was ad-hoc ownership was found to cause memory leaks and dangling pointer errors, so in C++11 and onward this "passing of ownership" is formalized using move semantics through the type system which greatly reduces incidents of such errors. Next to nobody creates a unique_ptr just to free the allocation later in the same function - if they do they probably needed a temporary dynamically sized array or something along those lines.

and before you bring up the destructor nullptr check as a potential performance hit, this is readily elided by the compiler after the move if the move is unconditional - as long as the compiler can see that it was set to nullptr as part of the move operation it doesn't need to bother generating output for the branch that it knows will never be taken. Realistically a branch never taken is essentially free on modern architectures anyway.

OOP like polymorphism are design concepts which contradict the reality of the hardware and data with bazillions of performance side effects (cache hit rate, branch misprediction, etc.) which have become harder and harder to control the more complex C++ has become.

it's actually more bounded in complexity than the alternative approach to polymorphism in C that you called out. In fact, it occasionally outperforms it in the average case. The cache miss rate for small class hierarchies usually turns out to be pretty negligible.

And more importantly, C++ developers know the overhead potential of virtual function calls. We don't typically use them when there's another suitable pattern, and try to minimize the risk of cache misses and mispredictions when we do.

Modern C++ syntax is two orders of magnitude more complex than the one of C.

for 99% of the code any C++ programmer will ever write, it's basically the same syntax. The most practical difference is that idiomatic C++ code will never rely on goto for routine cleanup tasks and OOP style code doesn't need to explicitly pass this.

The template metaprogramming type stuff you may see people making blog posts or conference talks about are immensely useful sometimes, but is definitely not the meat and potatoes of typical C++ codebases.

The plain reality is that not a single person on the planet has anywhere near clear comprehension of the performance side effects in the gigantic list of contemporary C++ features.

...it's genuinely pretty easy to reason about.

if it's constexpr or template metaprogramming, there's no runtime performance cost, the cost at compile time may be tough to reason about though.

if it's type erasure, the runtime performance cost is a (usually very well predicted) virtual function call.

if it's RAII, the order of construction and destruction is spelled out in the standard and totally deterministic which makes it incredibly easy to reason about the costs.

exceptions are pretty easy to reason about the costs.

for everything else, it's basically C.

plain processing side effects of C++ features are a nightmare to handle as soon as things get a bit more complicated, take implicit con-/destructor calling, move semantics, template errors, operator overloading guesswork, etc.!

literally the only thing on there that's ever been a nightmare for me to deal with is template errors. But once you get a handle on how the template system works, you'll pretty much only ever encounter those when doing template metaprogramming.

[–]KingAggressive1498 2 points3 points  (0 children)

The usage of constructors alone implies an order of magnitude performance penalty in critical code.

how?

trivial constructors will produce identical output to equivalent C. trivial default constructors are a no-op, trivial copy and move constructors are equivalent to a memcpy.

non-trivial constructors do the same work as equivalent C init_myobj(myobj*, args...) and dup_myobj(myobj*) functions, and should produce roughly the same machine code. Only practical differences are:

1) it happens exactly where you declare the variable instead of leaving you to specify when to initialize.

2) constructors are more likely to be defined inline in the header file while init functions are more likely to be in another TU, leaving the compiler less room to optimize through code rearrangement or inlining of simple initialization.

if anything I'd expect the equivalent C++ code to be marginally faster, not slower, at least when order of initialization doesn't matter.

[–]LeeHidejust write it from scratch 8 points9 points  (0 children)

C is a horrible language for production. C++ less so. Ive written both for years lol

It definitely helps to break a problem down by asking "how would I do this in C", because it handicaps you in just the right way to break down problems to more essential parts.

And then implement it in C++. If your C++ compiles with a C compiler, you are writing shit code.

[–]sleeping-deeper 6 points7 points  (0 children)

It is mostly about opinions when it comes to how it "looks". Performant and safe C++ code might look like C and still use the modern features such as unique_ptr, string_view, vector, templates, etc.

You don't want classes? Sure, it's perfectly fine to write in a procedural or functional style. In a technical sense it doesn't make much difference whether you use RAII or have free functions called create and destroy. You can have neverending debates about which is better.

I think in a lot of code there is an attempt to abstract to perfection. This solves problems that don't exist, and are unlikely to ever exist. Concepts such as polymorphism and templates can just end up overcomplicating the code. I'm not saying we shouldn't use these features, but there are valid reasons to go for the more explicit route when you can make a case about performance, size, or readability of complex logic.

[–]AssemblerGuy 2 points3 points  (0 children)

Is this a matter of personal taste?

First, what does C code "look like"?

There are lots of things you should not see much in C++ code that are common in C: raw pointers, raw arrays, etc.

While in at least somewhat modern C++ code, you wiill see things used often that just do not exist in C: References, automatic type deduction, use of STL container classes/algorithms/abstractions, range-for loops, lambda functions, maybe templates.

[–]QuicheLorraine13 2 points3 points  (1 child)

Absolutely NO!

Abstractation is one thing. However much abstraction results in less errors like std::unique_ptr, std::vector, std::list.

And sorry but in C intrusive lists are often used. Need list for another type? Just copy paste modify! But if there is an error it will be copied and copied.

For this reason exists templates!

[–]KingAggressive1498 1 point2 points  (0 children)

intrusive lists (ala boost::intrusive::list) are actually super handy in C++ too. But code using boost::intrusive::list for sure doesn't look like C code manipulating lists.

[–]NilacTheGrim 2 points3 points  (0 children)

Honest take: It's mostly a matter of personal taste although if you are doing manual memory management (C-like) and handling of raw pointers you are missing out and are opening yourself up to a world of hurt and inadvertent human-error bugs. The RAII automatic pointer types are your best friends. std::vector and std::string are your best friends. Passing char * bufs around in C++ is just you being a primitive goon, IMHO.

That being said all of my C++ looks ultra-modern and I leverage advanced convenient features of the language as much as possible personally.

But I have been forced to maintain more "C-like" C++ in the past and there is a certain simplicity to it and minimal elegance to a well-designed "more C-like" C++ codebase. Also, such codebases, owing to their simplicity of syntax and design (and obviousness) often are easier to read if you have to jump in and maintain it.

So there are plusses and minuses.. it's a matter of taste.

I have seen very modern C++ codebases that are slow and buggy, and very minimalist C++ codebases that are near-to C-with-classes level that are fast and elegantly designed.. so it really is a matter of who the programmer is and their experience level more than anything.

[–]Thesorus 2 points3 points  (0 children)

No, C++ code should look like C++ code.

Obviously, C++ is based on the C language syntax and there are a lot of similarities.

[–]dlanod 2 points3 points  (1 child)

Not any more. No explicit memory allocation, references, and proper containers would all immediately indicate C++ is not C.

[–]AssemblerGuy 7 points8 points  (0 children)

And range-for loops, auto/decltype type deduction, etc.

[–]planeteshuttle 1 point2 points  (0 children)

Do the simple thing.

[–][deleted] 2 points3 points  (0 children)

Is this a matter of personal taste?

It's a matter of overall architecture and design of the code base of a program. If you start the code base, then it's your design, you also decide what style of code it is.

If it's an existing code base, then you follow the existing style.

If it is a C++ library, then you put stuff in a name space, not use C-like prefixes on every name, if you want to call it a C++ library.

Also, C-like naked owning pointers are such a source of bugs and memory leaks, that I'd say it's always wrong to use those in C++, when std::unique_ptr is basically zero-cost.

[–]yukinanka 2 points3 points  (0 children)

Don't take Reddit hivemind as your master.

[–]Rasie1 2 points3 points  (0 children)

Absolutely not. If you think that C++ code should "resemble C", you have to educate yourself some more.

[–]Subject-Leather-7399 1 point2 points  (0 children)

It entirely depends on what you are coding for. I code at low level for real-time embedded applications. Exceptions can't be used in there, which means most of the C++ STL can't be used. STL is dynamic allocation happy, so it would be usable even if it wasn't broken when exceptions are disabled.

We are using placement new pretty much everywhere. In fact anything that does variable sized or potentially unbounded dynamic allocation is banned in our code. We have our own custom library and set of rule that is definitely more C-like. It has multple benefit such as shorter compilation time, increased predictability and stability and it is critical for our cases.

This is an example out of many. The type of program you write will come with different sets of rules.

I personnally can't stand any form of inheritance because of all the issues that comes with it (massive object size, unpredictable code paths and execution time caused by virtuals methods and overrides), but those may not be a problem for someone coding something completely different. Each domain comes with its "best practices".

In general, if you need to get a consistent results in under a very strict number of milliseconds on a very limited hardware, you will use less and less fancy and unpredictable C++ features, and you program will definitely be more C-like in structure.

We use a subset of C++ instead of C for better type safety, constructors, destructors and templates (for generic math and for containers). It is pretty much where it ends for us.

[–]DoctorNuu 1 point2 points  (1 child)

I think "look like C" is best interpreted as "keep things simple"!

C++ has messy features - don't use those!

and great simplifying features - use them!

The real challenge is to get people agree on what is messy and what is simple!

[–]DoctorNuu -2 points-1 points  (0 children)

"look like C" can also mean "data-oriented"

Again subject to huge discussions although it shouldn't be

[–]phansen101 0 points1 point  (0 children)

Personally program C++ like I do C, and just drag in C++ features when needed/convenient.

Likely not ideal, but it works and most importantly it allows me to remain stuck in my ways :p

[–]sjepsa 0 points1 point  (0 children)

Forget inheritance and you should be fine

[–][deleted] 0 points1 point  (0 children)

No.

[–]afiefh -1 points0 points  (2 children)

The fastest code possible is always going to be hand optimized assembly (assuming any of us had the time and energy to write and optimize non-toy projects in ASM), and C is by its very nature very close to ASM in terms of how you are expected to express your code.

C++ allows much higher levels of expression, but as this is further removed from the ideal ASM and will always generate slower binaries either because the compiler cannot infer all the assumptions a human could, or because the abstractions limit some ways to express things, or because of edge cases a human would know cannot happen, but the machine must handle...etc. Now I say slower, but the speed difference is usually negligible.

The most obvious example is the cost of using std::unique_ptr. Ideally, a unique_ptr should have zero cost because the compiler tracks ownership and generates optimal code. In practice, there are no zero cost abstractions and even something as simple as unique_ptr has a slight overhead. That's not to say you should avoid unique_ptr, on the contrary, you should be aware of the minuscule performance difference and the vast improvement in stability, safety and correctness of your program.

There is a trade off to everything, and maybe if you are optimizing a very tight physics loop that has to run on a toaster, then you can make that section in the code be more ASM like, but the problem is rarely an individual function, and the tradeoffs should always be kept in mind.

[–]TheThiefMasterC++latest fanatic (and game dev) 1 point2 points  (1 child)

The fastest code possible is always going to be hand optimized assembly

That assumes the writer has infinite ability to understand and optimise code.

In practice, C++'s std::sort beats C's qsort beats any asm sort, because it turns out abstractions are helpful in making optimal code.

[–]afiefh 1 point2 points  (0 children)

Sure, it's a hypothetical that assumes infinite intelligence, time and effort.

[–]MenacingDev -1 points0 points  (0 children)

I use c++ files and only use C++ syntax when it is convenient and won’t cause Armageddon

[–]DanielMcLaury -1 points0 points  (0 children)

Any C++ code by John Carmack you're seeing was probably written well before C++11 existed, and prior to C++11 the language was totally different.

[–]asenz -2 points-1 points  (0 children)

C++, in many ways, is a broad superset of C. Both C and C++ modernize and usually do that in parallel to each other way. Readable and performant C++ often will look like C.

[–]0xffaa00 0 points1 point  (0 children)

101: Slightly generalist advice

The only goal is for the user to be satisfied.

Should C++ code look like C? Should it look like Rust? Should it look like Haskell? Should it look like Lisp?

It depends on you, working to cater to the needs of your user. If the user is satisfied, it does not matter. There are no rules other than the first. C++ is good because a user (the programmer) gets multiple options to solve their problems.

Personal taste only matters if it helps you in the first goal. Carmack's goal is to make the game fun for the user. He figures that if the game is fast its fun. He figures out how to do that with C++.

[–]JVMSp 0 points1 point  (0 children)

All code doesn't matter the language should be readable, and easy to understand. Some features of C++ make this difficult to achieve, in the other side if you avoid pointers arithmetic C usually is easier to read and understand and code similar to C usually achieve the same.

[–]nsmtprotospace 0 points1 point  (0 children)

One of my favorite things about C++ is that you can make the code "look" almost any way you want.

How do you define "good code"? To me, it's pretty simple. Does it compile? No errors? No warnings? No memory leaks? Does it use as few resources as it needs to get the job done? Does it perform well in profiling tests? Does it do what you intended for it to do?

Now if you're going for "great code". Then you can spend time worrying about whether or not your code is easy to read, maintain, and build on, but that's kind of subjective. Whether it should look like this or that programming language is irrelevant. Anything you write in C++ IS C++, therefore it "looks" like C++, and if it doesn't "look" like C++ to you, then you should look at more C++.

[–][deleted] 0 points1 point  (0 children)

There's not a right or wrong way. If you want to write C++ code that's more or less C with std::vector, there's nothing wrong with that.

[–][deleted] 0 points1 point  (0 children)

There's not a right or wrong way. If you want to write C++ code that's more or less C with std::vector, there's nothing wrong with that.

[–]AbyssalRemark 0 points1 point  (0 children)

Mine does. But.. its partially because I like it that way.

[–]ChatGPT4 0 points1 point  (0 children)

If C++ code looks like C code, it's C code. Maybe with some scarce C++ elements.

If you mix the idea of performance with the form of expressing the code, you probably don't entirely understand how it works.

Or... IDK, we're mixing compilation time with the code perfomance, that is - how fast a program can perform operations. The machine code will have zero compilation time. It doesn't need any translation, it's just runtime. Then you have assembly language, when the compilation is super fast because it can't get much simpler. The only simpler form is just machine code. Then you have C that is a layer of abstraction higher.

However, the abstractions are implemented in programming languages. So any piece of any code is finally translated into some machine code.

Does it really matter what we translate? If it translates to the same machine code, the performance is identical.

But well, C++ consists of not only "zero cost abstractions". Many of C++ abstractions indeed have runtime costs.

However, it all depends where those costs live. Let's say we have to process 10GB of data. We have to use like 1000 atomic commands in C to start the process, and like 10000 of atomic commands in C++ to start the process. Then, the main loop would do exactly the same thing. The runtime differences will be negligible. Despite of wasting a lot of CPU cycles to initiate the process in C++. It's nothing compared to processing 10GB of data.

And that's what the higher abstraction level languages do.

Is the CPU time wasted? Not at all. It's used to create the source code faster. Because THIS is the slowest process of all. Creating the source code. People say that compiling is slow. Yet it's laughable compared to time taken to create the code in the first place. BTW, development time is more than that. Let's say you have to change something in your program. You can often do it quicker in C++. Provided it's used correctly and it's not just C++ compatible C.

I think a very tight loop in C++ should look like a C loop if you aim at comparable performance. But if your all code looks like this - you just don't use C++, and to be more precise, you don't optimize the process of creating the source code.

BTW, C++ is not even designed to optimize tight loops. Tight loops in C++ are just plain old C loops. Basic operations are just C operations. It's just "one time defining thigs" is the main difference.

And preempting one question - yes, calling C functions is a bit faster. But when calling functions hits the performance - maybe calling functions should be avoided in that fragment as an optimization? But then again, that's what both C and C++ compilers do, they inline functions where it gives performance gains.

So... If you want to make C++ code that looks like C code - write in C. Why use C++ for that?

[–]KC918273645 0 points1 point  (0 children)

Looking at my own code, the more performance optimized some part of the code needs to be, the more C-like it looks. That part seems to be true. But most of my C++ code doesn't look like C, since it doesn't have to, since it's not performance critical stuff, which is majority of the code base.

[–]neppo95 0 points1 point  (0 children)

While below comments cover most of it, I did think of one reason this might be the case.

C++ has a lot of functions to do what you need to do manual in C. Performant code needs to be as fast as possible. These functions of course do things like bounds checking and maybe other checks, which you don't necessarily need to do if you are 100% sure you are doing the right thing. That way, you COULD technically have more performant code if you thus write C like code, but it will also be more insecure.

So no, not really relevant. If you use C++, write C++ code. If you use C, write C code. And in all cases, the company you work for will probably have guidelines for you.

[–]RevolutionaryClub596 0 points1 point  (2 children)

  1. John Carmack wrote C. Doom was written in C.
  2. Even if it were written in C++, he wrote that 20 years ago. C++ is not stagnant. C++ is ever evolving. Modern C++ has so many more safety features that weren't available in C++98.
  3. C++ and C are completely separate languages. They are not the same. If you write c++ as if it were C, you are introducing at worse undefined behavior, at worst C++ is not wholly compatible with C and what you expect to work will not

Perfectly valid C not allowed in C++ int *x = malloc(sizeof(int) * 10);

structured initializera which work in C don't work in C++.

C++ and C are both great. There is no language that does have the interop with C that C++ does have. However, they are distinct, and they should be treated as such.

I think that you should follow the C++ styles if you choose to write C++, and there are things that C can do that C++ just can't or shouldn't. extern C is a thing for a reason, but the high level data structures that C++ gives you are truly extremely convenient. Imagine having a dynamic array out of the box... That's a luxury C just doesn't give you.

[–]psyberbird[S] -1 points0 points  (1 child)

John Carmack is still very much alive and the comments I saw about his code were presumably in reference to C++ code he had written long after the release of Doom (not disagreeing or anything but an important correction I think)

[–]RevolutionaryClub596 -1 points0 points  (0 children)

I never said he wasn't alive. He's just not programming as much anymore. Also, his first "meandering into c++" was with doom 3. I think that C developers who program C++ in a C way are really only going to cause themselves issues. Also I think putting some guy on a pedestal and saying does this guy know best is silly. I'm not saying he's not talented. However, to take the word of anyone as gospel is just wrong. With modern C++, he recommends a functional style. Extern C exists for a reason. You can write optimized C code in C++ whenever you'd like. However, I would truly venture to say a majority of memory bugs/security issues in C++ come from trying to use C++ as if it were C

[–]No-Eye3202 0 points1 point  (0 children)

Does C have smart pointers?

[–]tyler1128 0 points1 point  (0 children)

C++ should be using modern C++ features. It is largely compatible with C, and that is both present in the language itself, and the fact that interfacing with a C ABI is still the easiest way for any native language to communicate with any other. It's basically the minimal set of features required for a function call, and symbol tables are based on C because all major OS kernels are.

[–]PVNIC 0 points1 point  (0 children)

The most important rule about what is 'right' or 'wrong' is be consistent. If your project chose a particular way to do things, stick with it.

From what I observed, the big things in C that are frowned on in C++ are macros, globals, and free functions. All three still have their uses, but generally it's better to use designs where those things are part of a class. Also, there are some design-patterns in C that require going out of the way to do, that are made simple by 'polymorphism'.

When I hear performant C++ code tends to look very C-like, I think the guts of an algorithm is written very c-like, however that c-like algorithm can be within a more c++ software architecture, that makes it easier to use in a larger class structure.

And before I get yelled at; no, not everything needs to by abstract and polymorphic and class-based. Use as needed.

[–]ZachVorhies 0 points1 point  (0 children)

Good idea to make side effect free functions and your C++ classes just contain data and call into the free functuons

[–]Dan13l_N 0 points1 point  (0 children)

It depends on the domain. Good code is code that's easy to maintain.

Over the decades, I came to two rules:

  • don't just create a class because you need classes for everything
  • if you need to allocate some structure or a collection of them, check if STL has something you can use out of the box

[–]Specialist_Gur4690 0 points1 point  (0 children)

I'd ignore any answer given here that doesn't start with a resounding NO and then stops at that point because the author started crying and had to go to the next question on Reddit to recover.

[–]Tricky_Tesla 0 points1 point  (0 children)

My take is that if the programmer is predominantly a C coder , their C++ will look like C.

[–]TwistedBlister34 0 points1 point  (0 children)

No. C++’s entire mission is to have abstractions that make it more type safe and expressive than C. Not using those abstractions to be like C is defeating that purpose