42 comments

[ 4.2 ms ] story [ 71.6 ms ] thread
> #pragma once

Is there every a scenario where this is not needed in a header file?

some headers are meant to be included multiple times with different definitions (although templates are usually a better way to do this).
Yeah I used to see it from time to time, but it’s been w long time now. It seems to me that modern C++ doesn’t really need it unless you’re doing something like including data files (rather than source files) multiple times.

Having said that, I almost did something like that recently, to initialise some generated data structures (include once for the structs and again to initialise memory pools), but I ended up finding a cleaner way that didn’t need it.

It happens occasionally. An example would be that there are a couple of libraries that do "generic collections" in C using the preprocessor (instead of using templates). So, you could imagine something like this:

    #define VEC_TYPE double
    #include <c-vector.h>
    #undef VEC_TYPE
and that would provide you a type like "vec_double", a vector specialized for the double type. You'd want to import that once for each vector type you wanted.

It's not the most common use-case (and you can argue if it's a good idea at all), but it is at least a reasonable reason why you'd want a header file without #pragma once.

strong disagree on printf... maybe there's some modern typesafe C++ way to do the same thing, but format specifiers are way easier to remember than all the ways of modifying iostream.
agreed. The printf style API is great for deferred/asynchronous log formatting via serialization.
If you're interested, check out Abseil's StrFormat [0] for a typesafe printf replacement that uses format strings. You get a nice speed up as well.

[0] https://abseil.io/docs/cpp/guides/format

It's blows my mind that something like this hasn't been in the standard library for, like, 20 years. Like, why can't i do this?

   std::string str = "Values: ";

   for (auto &[key, val]: dict) {
       str.append("%s: %d, ", key, value);
   }
This is so much nicer to use than stringstream, and it's also so much more efficient! You could allocate a "big enough" string at the beginning, and then you would have no more allocations. You could also reuse the string, so you can avoid almost all allocations for the lifetime of the program. This is basically impossible to do with stringstream: it's just insane about copies and allocations, and it's more or less impossible to reuse buffers.

The C++ format strings are an improvement, but just this simple thing (a super straight-forward translation of printf to C++ std::strings) is basically all you would ever need. C more or less got it right in the 70s, and then C++ spent decades with its own (significantly worse) solution.

The original reason is that it was not possibly to implement in a type-safe manner with c++98, as it lacked variadic templates. The << stream << stuff was always a compromise.
And at least on clang, printf is somewhat typesafe. If you write %s and pass an int, it will give you a warning. This also works if you are using a wrapper that ultimatively calls printf!

Constrast with cout, which will happily let you print out the wrong variable. It won't print out garbage of course because it helpfully knows to print out the int differently, but it can't give you a warning. Also, it's difficult to do localization with it.

I am not sure about his example of large classes. If "energy()" is an integral part of the model, why shouldn't it be a method of it? Maybe the example is not well chosen, but it looks more like extreme minimalism to me. If there are different kind of models (like Ising and XY), they could all implement the same interface with an energy method. Any views on this?
>If there are different kind of models (like Ising and XY), they could all implement the same interface with an energy method. Any views on this?

If it's necessary for the interface, then it should of course be in the interface. But he was referring to the unnecessary placement of internal implementation functions into the class. This is all over the current codebase and it drives me _crazy_. In a healthy codebase, a header file is the best documentation yiu can have of a class, and polluting it with unnecessary functions that have no meaning to the user makes it harder to read and reason about.

I still don't understand why the magnetization or the energy of the Ising model should be "unnecessary internal implementation functions" when they are the actual outputs of the model.

Thinking more about it, the example is (almost) a value object class. I don't think this is well-chosen for making a point about bloated classes.

IMHO most of those are not general "C++ anti-patterns", but instead define a personal C++ coding style (e.g. another C++ subset). It's important for teams to have such a coding style / C++ subset, but that doesn't mean that others are better or worse.
I'd say it's a mix, but most of the anti-patterns listed seem to be pretty 'objectively' wrong, and not sensitive to house style. They generally aren't just subjective preferences. My thoughts on the first few:

Lowercase preprocessor constants. Doing this contradicts an established convention in C and C++, even used by the standard libraries. You simply shouldn't deviate from this convention, all you will do is introduce confusion. It's hard to think of a good reason to ever do this.

Preprocessor over scopes. This generally confuses and complicates things, but makes sense in rare circumstances. BOOST_SCOPE_EXIT does something like this, for instance.

Including source files (from a header file). Yep, that's a bad idea. I don't see why template specializations would be an exception.

Unnamed #if 0 branches. This doesn't strike me as obviously terrible. Using meaningful indentation would help here. Introducing a macro constant strikes me as introducing a new kind of complexity, given that the goal is just to comment out code. If the goal isn't to just comment out code, but to do 'conditional compilation', then that's different.

Standalone header files. Yes, a header file should include all the other headers it needs. Doing otherwise is sloppy, and no-one will thank you for wasting their time having to fix your headers.

Assuming that unconditionally enabled or disabled #if 0 and #if 1 sections imply some implicit flags or logic relating them to one another is particularly illogic. Unless the author has been exposed to someone who used them in complex ways as a poor man's version control system, of course.
> Unnamed #if 0 branches. This doesn't strike me as obviously terrible.

Your editor has a key command to comment/uncomment a block of code with line comments, use it. Similarly, C++ has syntax for comments, use it...

As the article mentions, unlike D [0], C++ block comments can't be nested, which is a point in favour of the #if 0 trick.

I agree it's generally better to use line comments using a good editor. This has the advantage that it plays nice with git diff, which will faithfully show every affected line. If you use block comments or #if 0, it will only show the 2 new lines that start and end the new comment block.

Trivia: Opposite to C, which originally had block comments and not line comments, the Ada language only supports line comments, with the reasoning that it's clearer at a glance whether a line has been commented out (especially in the absence of syntax highlighting).

[0] https://wiki.dlang.org/Commenting_out_code#Nested_comments

I know the reason why its done, and again, this is not a problem if you know how to use your editor.

The real anti-pattern is commenting out code to remove it. Delete it if not necessary, line comment anything else.

I think "template specializations" might have been the wrong term, and what was meant was "template implementations".
Perhaps I'm missing something I still don't see why you'd ever #include a .cpp file from a .hpp header file.
You can't separately compile and link in templates. The compiler needs to see the definition to instantiate it.

Meanwhile you might not want to put that definition in the header file so the header just contains the interface. Ergo you #include the implementation at the bottom of the header file.

That isn't an 'implementation file' in the relevant sense.

If the file is intended to be used with #include, it's a header file, and should be given the .h or .hpp extension. If the file corresponds to an object file in the build, it should be given the .cpp extension (or .cxx).

If you want to break apart your template metaprogramming into several .hpp files, you can do so.

Modern C++ is the anti-pattern.
C++ is an anti-pattern
While I understand where you're coming from, we can't just switch every project to Rust.
Interestingly, comments like the one you're replying to are the reason why I'm ignoring Rust.

It's reached critical mass, meme status, where people will come in and shill for Rust or shill for how bad C++ is, not for any concrete or logical reason, but just because they've seen other people say the same thing. Which then, by becoming yet another person memeing that idea on, encourages more people to hold and repeat the same opinion, which then encourages more people to hold and repeat the same opinion, and so on. It's a viral idea, there's no stopping it at this point.

The people repeating the same lines rarely have any actual reason to repeat them other than the fact that they think they're true since they've seen so many other people repeat them. They'll just come in and assert that "C++ is an anti-pattern", with no rationale, no facts, no arguments, it's more political than technical, it's a mantra they've been brainwashed into repeating since everyone else in their echo chamber is repeating it. Of course this same pattern of a viral idea/meme extends much further into politics and all other areas of our life, enabled by the internet / mass media.

Maybe objectively C++ is pretty horrible and Rust is great, I'm not even saying anything about the objective reality of the situation. It's just so easy to spot the patterns at this point, across all areas of conversation, that I now just by default ignore and assume false anything that shows the same viral pattern, because I know the viral pattern is usually backed by nothing but itself. Maybe in the past if enough people believed something to be true it'd be easier to assume there's some reason for them to believe that, but in the era of the internet / mass media that's pretty much gone.

I've worked on large C++ projects. Debugging hard to find data corruption, data leaks, data races was one of the most frustrating aspect. In a 200kloc project with 10 devs, there was always a crash worthy bug. Always. Automated tests could only catch so many things. Often the crash made it to the end user, causing them to lose valuable work and time. It sucked.

By far though some of the worst issues were ones involving heap fragmentation because of all the damn heap loving C++ classes and shared_ptr wrappers.

Rust doesn't solve everything. But it solves the most time consuming parts of C++ for me. Debugging. I've yet to need a debugger for Rust, after 5 years writing many 10s of thousands of lines of it. I trust the random crate I pull in won't segfault on something asanine if it stays away from unwarranted unsafe usage.

It's a breath of fresh air to not need a GC, be able to trust most other code won't cause a crash, and that once it compiles it's most likely going to work barring any logic bugs. It saves me immense amounts of time, and mostly avoids debugging hard issues in production. Mostly.

While people sit in their debuggers adding watch points, stepping through some 3rd party lib to figure out a crash, I'm busy working on the next killer feature.

I completely agree with you, I'm programming in C++ in my job and I've dabbled in Rust a bit. The issues in C++ are 90% ones that you wouldn't have in Rust

However my reply to the higher up comment was because you can't switch every project away from C++. I think it's a good idea to start most projects where you would have picked C++ in Rust now, just that it's infeasible for a lot of code, given the mountains of code that exist in C++.

Also plenty of code needs certifications that just don't exist for Rust yet.

So saying C++ is an anti-pattern is kind of correct, but it doesn't help you if you have to deal with it.

Most of these are just mistakes you see in code written by novices or non-professionals.

Although its symptomatic of a larger issue when working with production C++ codebases, which is that you need a lot of eyes on the code to prevent these mistakes and automated tools only go so far.

As always, -Wall, -Wpedantic, -Wextra, and -Werror are your friends. But you should understand the error before you fix warnings (which is what `double x = double(1)` looks like)

The advice about the order of includes is interesting, as it's the exact opposite of my usual habit. I see the point, and I'll definitely consider adopting it.

One thing I've been bitten by in the past is that Clang / libc++ on Mac sometimes doesn't require a standard header that GCC / libstdc++ does. I don't think this pattern will fix that, though.

The C++ Standard says that for any implementation, any Standard header file may itself #include any other header file, or not. It's thus up to the programmer to always #include the headers for the Standard functions they use, if they wish their code to be at all portable.
“I have asked why he did not just write T t; and be done with it. He said that he is used to the T *t = new T(); syntax. Then I pointed out that he has a memory leak. He replied that the runtime will take care of that. Too bad there isn't a runtime in C++ ...”

Yikes! I don’t know if I’d call this a “not using RAII” anti-pattern, it’s just sheer ignorance / incompetence of how the language works.