The PImpl idiom and the C++26 std:indirect type
mariusbancila.ro
mariusbancila.ro
And before it grows too big, it wastes memory. For your use cases it may not matter, and the saved pointer indirection may be more important, but maybe the person who has a million item vector of objects doesn't appreciate a 300% "just in case" memory overhead. The overhead may also hurt cache hits.
If you're doing this to save the pointer indirection, you should benchmark it for every use case, since negative cache effects may dwarf that gain.
Then again, extra padding can also help performance, for some workloads (especially multi threaded read/write against a vector of objects).
So without further context, there's no way to say if your way hurts or helps. It's certainly not a general solution.
GNURadio consistently uses pimpl for blocks, as I understand it mainly for ABI.
> willing to stop improving.
I think that dismissing it like that shows a naive understanding of execution environments, binary interface design, and in general systems software engineering.
If you want the latest and greatest, a willingness to rebuild your code seems like a reasonable prerequisite to me.
If you need a binary blob that can withstand toolchain versioning, use a dynamically linked library.
So regardless of what WG14 and WG21 do, compiler vendors will ignore them, if it means angry customers.
In design by committee languages, new standards are only relevant to the extent implementers actually care about them.
So you're only trying to solve compile time issues (incremental and not)? Maybe the right long term solution is C++ modules, instead? And maybe "just" a matter of having your build environment support modules?
how it wastes the memory? It's just a bytes array of the same size as struct. I would be more imposed on how idiomatic pimpl with heap allocation influences memory usage and cache hits
Because if it's an exact match, then it doesn't help at all with ABI compat, and arguably doesn't add anything. Well, aside from compile time, but that's maybe better solved with modules?
The pimpl idiom is a C idiom where a header declares an opaque structure and prototypes of functions that take pointers to that structure. In C the OP example would look something like
// widget.h
typedef struct Widget_t Widget; /* opaque! */
Widget* Widget_Create(const string* pName);
Widget* Widget_Clone(Widget*);
void Widget_Destroy(Widget*);
void Widget_click(Widget*);
int Widget_clickCount(const Widget*);
const string* Widget_label(const Widget*);
// widget.c
struct Widget_t {
int clicks;
string *name;
};
// ... implementations of the functions from the .h ...
In particular, in C `Widget` directly has `clicks` and `name` as fields.But in c++ we like to use methods on objects, and in order to do this, you need the class declaration in scope, which means your current compilation unit needs to have seen all of Widget's data members. In practice this means if you try to use "pimpl" in C++, you do something like the OP where there is a pointer to an opaque type inside your class.
However, this is not the same thing. Methods are called with a `this` pointer, which means every access to the internal structure adds a second pointer dereference. This is why this isn't the true pimpl -- it wastes an extra deref on every access.
You can get true pimpl in current C++ but it's a lot of boilerplate and heavily relies on compiler inlining. An implementation of the example from the OP: https://godbolt.org/z/6EznxeG1n . In practice this is too much work, hard to read, and so nobody does it.
For the c++ standards committee: please add an "opaque class" feature where the class can only define non-virtual method prototypes. Then the full class declaration, in the associated cpp file, could include its parent classes, actual data layout, and function implementations.
I am wondering why C++ can't implement "non-null" unique_ptr version in the same way? As I know, that the main argument against implementing it is, that it's can't be done, since move-out unique_ptr still can be null.
https://github.com/microsoft/GSL/blob/main/docs/headers.md#u...
What do you mean by move-out unique_ptr? That the not_null ptr type would ne null after it's been moved?
In that case that's just a plain usage error, same as how you could memset it to null.
I would love to proved wrong, but everything I can think of still leaves a footgun that's easy to trigger by accident, and thus negates the point of the solution.
I think the can't-reference-after-moved-from and objects-are-not-Copy-by-default are key to creating these types (at least enforced at compile time). And that would require major language changes, at least as big as the C++11 changes.
While many of us that like C++, would wish for a different evolution process, some of this stuff can be enforced by static analysis tooling.
Just like despite being safer than C++, we still use Sonar, FindBugs, FxCop/Roslyn Analysers, go vet, rust clippy, one more reason to actually use Sonar, clang-tidy, PVS, MSVC analyse,... with languages like C and C++.
In some of these you can add your own rules even, even if not always that straightforward.
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
In precompiled headers to solve that particular problem.To make hobby-coding fun, i use a mstdp.hpp that implements "naive" versions of unique,shared,function,etc that compiles faster than including just one of the std versions (and yes, MSVC versions of those libraries seem to be excessivly complex).
The import std is much faster than plain #include<iostream>.
Maybe it's time to move my hobby project over and see how well it works.
Best experience is VC++ with MSBuild, cmake/ninja work great with latest clang however import std support is not yet enabled by default in CMake.
I only care about VC++ for hobby coding, hence using modules.
See for example, https://github.com/pjmlp/RaytracingWeekend-CPP
But, no ICEs and it's working! I'll start writing some code with them tomorrow.
> Still best only used in implementation files, not headers.
We only compile implementation files!
Not sure how much pimpl is used in reality, but it's a pretty ok solution to speed up build times (apart from unity builds), because it avoids having to include headers that are only needed for the private state into the public interface header.
I also like to use it sometimes to "hide" private methods and their documentation into PIMPL, so the public header is kept clean.
Yep, that's what I've used it for. Didn't find it too difficult to implement it myself, but I guess every bit of convenience/bug avoidance helps.
With many of the features coming into the language over time, I kinda wish that a bit more restricted subset of it eventually becomes a thing, but I know in practice it might as well be a completely different language. That, and I expect that still many other things have not been resolved as well as they are elsewhere, such as build system and dependency management (although I haven't touched this stack for a while now, so I would love to be surprised).
You'd essentially like to derive a class/module implementation from it's corresponding class/module interface (which is all the user sees), but have the language automatically add a hidden "pimpl" pointer to the interface class. The implementation would then essentially use "this" to access public members, and "pimpl" for private members.
1. click() should not be a member of the widget. A widget does not click; a user clicks a widget. A click can change a widget's state, but the state might change because of other effects, e.g. pressing a key when the widget is focused. But then, that's just one of the issues with treating UI widgets this way.
2. More to the point - clickCount. If this is a button, it shouldn't keep a record, or aggregate, of its clicks within it; and if it's a widget where this does really matter, like a range control where more clicks mean a value that goes farther along the range - you still would not keep the count of clicks, but the current position. Statistics about the interaction with an object should not be part of the object itself. At most it might be legitimate to have, say, a Widget class, a template like <class Stats> StatisticsTracker , and then class TrackedWidget which uses that as a mixin, i.e. inheriting both Widget and StatisticsTracker<ClickStats>. And that's already stretching it beyond what I would find reasonable.
3. Having something named is another aspect of objects which may be a good fit for a mixin class.
Anyway, an 'indirect' type for objects you don't know the definition of sounds nice.
A few more nitpickis about the example:
1. Instead of explicitly applying the rule-of-0 with `= default` for the copy&move ctor&assignment and the destructor - just _don't_ write anything:
class Widget
{
public:
void click();
int clickCount() const;
std::string label() const;
private:
struct Impl;
std::indirect<Impl> pimpl_;
};
and that's the beauty of the rule of 0.2. Why return an std::string for the label? The label() method should return an std::string_view
This only works if it's always the same value. This doesn't work if the label is for example, set to `std::to_string(clickCount())`
> 1. Instead of explicitly applying the rule-of-0 with `= default` for the copy&move ctor&assignment and the destructor - just _don't_ write anything:
The blog post explicitly explains why this doesn't work. You have to define these methods in the source file because they need to see the definition of the Impl struct.
I was pondering on why he was putting the defaulted methods in the cpp, any particular reasons?
I did realize that the indirect version is required to be in the cpp since the header won't know how to copy without knowing the definition of the impl class.
All this complexity follows unique_ptr and copy constructor madness.
Anything with pointers with ownership should never be copied - period. Reference pointers - OK if scope/lifetime is known.
Can we have c++11 lite?
This "std::indirect" tries to have value semantics, but in fact it's just another type of smart pointer and uses pointer syntax (pimpl->foo : forced since C++ allows "->" as a user defined operator name, but not ".").
But it's a weird sort of "pointer" given this copying behavior, which is maybe why they didn't give it a "_ptr" name.
When you write new code, this is (mostly) not an issue; when you have to maintain old code, it is. Especially if the existing codebase is somewhat of a patchwork of code introduced at different points in time - pre-C++98, C++98, C++03, C++11 and so on. For this reason it is a saintly virtue to manage to unify the C++ "vernacular" used in a project, for better readability by newcomers and for facilitating uniform changes to the entire codebase later on.
Because they spend so much of their time struggling with the pain points of the older code.
> but can't intuitively understand what it's doing
For (most?) new vocabulary types, it is rather intuitive to understand what they do. optional, variant, indirect - you may not remember the details by heart immediately, but you get the general idea and expect that they would behave in some reasonable way. And mostly, they do. That's not to say they're perfect: I feel like vomiting looking at std::variant's and how you have to work with them, as opposed to a proper case classes / algebraic union types in the language itself. And yet - when someone puts one in their class, instead of a bunch of code in a bunch of methods, you know what's going on. It does "read like a different language" somewhat, and that's good. The nicer language has been struggling to get out, as the saying goes.
Even classes are an instance of this, they were to solve some perceived problems, but they created much bigger issues, such as readability issues and introducing many more compile time dependencies.
PIMPL wasn't even a C++ feature but an idiom pushed by some people. It is next to unusable because you have to duplicate the API and write all the call forwards.
One problem with std::unique_ptr for example is that there is no ergonomic way to use it to hide implementations. The reason is it relies on destructors and to use destructors the class definition needs to be visible.
This here should not need to happen:
> document that moved-from objects cannot be used
It's moved from, so of course it cannot be used. Shouldn't have to add an assertion in every method to guard a fundamental invariant.
For me your comment seems pointless, because std::indirect have a precise and clear problem which it solves. And this is totaly not a "hackery fix of language drawback". In this case language works as intended and std::indirect just close the gap for facility which is still would be written by hands, if there is no std::indirect.
This way I have already replaced a lot of for-loops by range-based for-loops. It helps me to understand code faster.
But code parts that noone needs to touch or see do not need to be more readable.
Nobody needs "deep copying", ever. It's not even well defined what it should mean (i.e. how deep etc.). It's purely a theoretical problem with no good practical (one-fits-all) solution. The only practical way is to copy what you need copied, when you need it. Done.
C++ is trivial compared to the code I work on. If you are writing hello world complexity then C++ might be complex but some of us work on hard problems.
I would argue that if cleaning up resources properly is among the hard problems, or among the most error-prone problems in your code, then maybe you're problems aren't that hard or complex after all.
I'm currently working on a distributed caching system and on real time voxel geometry boolean operations simulation (on GPU), both on the scale of >= 10^9. Is that "complex" enough? Both are done in C++. C++ helps exactly 0 in achieving any of these things (as opposed to using plain C), well the one help is I don't have to type 'struct' all the time.
In fact, in one of these projects I was pushed to use STL initially. I'm now working on getting rid of the last of them because we have had concurrency bugs and performance problems from using them. The code was not obvious and using STL containers (std::deque is very bad specifically) meant the actual runtime characteristics depend on which STL implementation is being compiled in. It would have been easier to just do straightforward obvious manual code.
> (as opposed to using plain C)
Also, curious - how plain C helps with this?
> how plain C helps with this?
By staying out of the way and providing everything of what you actually need in the end. That is assuming a detail oriented approach where you deeply think about, and want to be flexible about, the organization of what your program should do. As programs grow into large architectures, and as programs get more performance conscious, they also get more detail oriented like that, and they tend to opt out of unflexible high level language features.
Which part of the STL are you expecting to be thread safe?
If you'd been asking in good faith, the question would have been, "how did the concurrency problems look like"?
To which I'm going to answer, one of the bugs I hit was due to unexpected invalidation of a std::deque iterator. This came from being mislead to use std::deque as a quick & dirty implementation of a producer-consumer queue, and keeping iterators to track the read and write positions. Almost nobody has actually used std::deque (I hadn't either) but there is a common understanding (perhaps misunderstanding) that it is something like a chunk-queue. That vague understanding led me to believe that I can (and should, to avoid O(n) random access ) keep iterators after write operations. And using them that way did work for quite some time, I only hit confusing issues later.
(Actually random access is specified to be O(1) but this is even less widely known and makes std::deque a quite arcane data structure).
The problem with an abstract iterator interface here is that it doesn't help understanding what std::deque actually is. In case of std::deque, keeping read and write cursors works mostly fine, but it stops working (for example) if the read cursor pointed to the current end (was equal to deque::end) and the deque gets an append, which will invalidate the old end (read) cursor.
This is a good example of the complexity we have to deal with if we don't want to write a simple straightforward solution from scratch (chunk list) but instead code against something that we don't understand well. Not trying to use the STL but instead doing straightforward low level code would have made potential pitfalls more clear, and would have made bug search easier. It would have required less work to get the code to a working and maintainable state.
Another problem with std::deque is that the sizes of the chunks are not specified. They vary wildly between implementations, such that you can in practice get no performance guarantees from using std::deque, unless committing to a specific STL implementation (which is rarely practical). In fact, it is not even specified that deque uses something like chunks internally. It's too abstract to be useful.
This isn't a problem with the standard library because a std::dequeue or any other core data structure doesn't make any promises about concurrency.
You can find details of iterator invalidation here.
https://cppreference.com/cpp/container/deque
If you have an underlying data structure that is being used from multiple threads, you can't hold on to raw pointers into the data structure. There is no way for other threads to know that it can't be changed, moved, freed or invalidated.
You need to copy the data out while a mutex is still locked (if doing simple mutex style concurrency) or you need to hold a reference count in the object that is returned so that the underlying structure knows that it can't touch that data from other threads.
I hope it isn't lost on you that the reference counting approach is much easier to do with a destructor, since the reference count can be incremented before it is given to you from the API and decremented automatically when it goes out of scope.
If you want some good concurrent queues for C++, look at this person's work:
> This isn't a problem with the standard library because a std::dequeue or any other core data structure doesn't make any promises about concurrency.
Dude, I KNOW I need to handle concurrency myself. But I'd contend the point that it isn't a problem with the STL: It is a bug (that I introduced myself) that I had to deal with because of complexity, or rather because non-obvious behaviour, because bullshit boilerplate.
> If you have an underlying data structure that is being used from multiple threads, you can't hold on to raw pointers into the data structure. There is no way for other threads to know that it can't be changed, moved, freed or invalidated.
This is totally irrelevant because if you paid attention, the problem wasn't even threads. It was concurrency, more abstractly. Iterator invalidation based on the "manifested" order of execution.
But anyway, you want to jump to reference counting. I'd say you can absolutely hold on to raw pointers from multiple threads, it entirely depends on what you do. If the threads have unpredictable lifetimes, then yes, some form of reference counting is indicated.
But when you know that isn't the case, then it isn't the case and you probably don't need reference counting.
> I hope it isn't lost on you that the reference counting approach is much easier to do with a destructor, since the reference count can be incremented before it is given to you from the API and decremented automatically when it goes out of scope.
Except when you're passing around stuff and have to duplicate or move references, and have to use APIs that receive pre-incremented or un-incremented pointers. In some cases your data structures might even be so messy that you end up with cycles.
I have my scars from making my own COM pointer classes with copy and move semantics, and also from using "official" COM pointer classes. After a couple of iterations I've decided to cut all the boilerplate and C++ ceremony that doesn't do anything, and get rid of ugly method wrappers that are a pain to step through in the debugger, and stopped clinging to a cargo cult which simply leaves you with harder to detect bugs.
You heard right, I'm back to completely manual reference counting (and only counting where I _have_ to), somehow the code is much shorter and easily understandable, I got back control over what happens. Have been able to keep atomic ops at a minimum, with RAII superfluous ops can happen easily. (Remember Chromium's 25000 copies per keystroke bug?) And there has only been a single instance where I introduced a leak, that was immediately pointed out by the D3D11 debug layer. I'm doing this approach for my second project already and have found it to work great.
There is no solution except good understanding of what you do, and good code structure that expresses this understanding. Generic "RAII" type understanding is rarely helpful IMO, you give up control and sometimes end up throwing hands in the AIIR and hope it will not break.
> https://github.com/cameron314/concurrentqueue
Thanks for the pointers to what is probably 5K lines of C++ boilerplate. But I have written half a dozen concurrent queues myself, locking and lock-free ones. Some in less than a hundred lines. Also one in ~2K lines, that was for a longer-term project where the queue needs to safely persist to disk every couple of milliseconds, while ingesting millions of messages per second and billions of bytes per second (was hitting the ~2GB/s that I could get out of my flash drive).
If you want an approachable source that leaves out the fluff, I'd recommend 1024cores by Dmitry Vyukov (only issue is formatting).
I gave you great information on how to make your queues thread safe again.
I don't know where this expectation comes from that you can reply to me and I can't reply to you. If you don't want to continue you don't have to reply.
This is totally irrelevant because if you paid attention, the problem wasn't even threads. It was concurrency, more abstractly. Iterator invalidation based on the "manifested" order of execution.
This is really just mixing terms. You aren't going to notice all your concurrency bugs without threads. If you're holding a raw pointer to an internal resource of a data structure while other threads can modify it, you aren't going to see all your bugs until multiple threads are modifying and reading from the queue.
If the threads have unpredictable lifetimes, then yes, some form of reference counting is indicated.
It isn't about threads having unpredictable lifetimes, they could all be running at the same time and have predictable lifetimes.
In some cases your data structures might even be so messy that you end up with cycles.
Then don't do that.
Thanks for the pointers to what is probably 5K lines of C++ boilerplate.
Lots of people get a lot of good out of them.
But I have written half a dozen concurrent queues myself,
You might want to benchmark and test those bad boys thoroughly if you think you can hold a raw pointer into a data structure that can change from other threads. If you use a template you won't have to rewrite them over and over.
Also don't forget that allocations can lock and that your double allocations of the struct and data in a data structure can amplify that.
If you want an approachable source that leaves out the fluff,
Thanks, but I haven't made the same assumptions about raw pointers in concurrent data structures then blamed the STL, so I haven't had the bugs that you're talking about here.
"Great" is quite debatable. In any case, nothing I hadn't already known.
> You aren't going to notice all your concurrency bugs without threads.
True, but my problem was neither proper locking / thread safety, nor reference counting.
You still felt the need to explain to me because you don't realize the problem isn't that I don't understand what you say. The problem is that you don't understand / don't want to accept what I say, and you prefer assuming I'm talking out of my ass.
> Lots of people get a lot of good out of them.
Well if they don't want to create and understand their own but instead prefer to invite tons of unnecessary boilerplate to the point where you can't find the actual functionality -- good for them.
> You might want to benchmark and test those bad boys thoroughly if you think you can hold a raw pointer into a data structure that can change from other threads
I DO NOT THINK THAT. Why do you keep implying that my thinking is wrong? That is so arrogant of you.
Reference counting (how you keep something alive) is completely orthogonal to the queue's functionality. In my case, the queue was used as a "global" kind of object, so no reference counting needed.
> Also don't forget that allocations can lock and that your double allocations of the struct and data in a data structure can amplify that.
In general I avoid unnecessary allocations, where did I imply making "double allocations"? What I argued is that indirection may not be as bad as you think, may in fact be the correct way to make your program both more maintainable and more performant.
I try to organize memory allocation upfront to keep memory local to subsystems, which reduces or avoids contention in many cases (for example there might be only a single thread doing allocations for a subsystem at a time).
> Thanks, but I haven't made the same assumptions about raw pointers in concurrent data structures then blamed the STL, so I haven't had the bugs that you're talking about here
You're arguing all the time for just buying into stuff as a cargo cult, I'm only trying to describe how much weight all this ceremony introduces, which makes it more painful to maintain, makes it more likely to introduce bugs, and harder to find bugs. Don't explain basic C++ stuff to me. I understand it. What I'm saying is that this is not the best way to write things at all. There's a lot of "abstraction" slop that brings more downsides than upsides.
But I'm sure you never run into this type of problem... Good for you!
I at least showed you cppreference so you can look up the data structures and their guarantees.
True, but my problem was neither proper locking / thread safety, nor reference counting.
But you did blame the STL for concurrency bugs so there must have been something.
prefer to invite tons of unnecessary boilerplate
I'm not sure a heavily tested and fast lock free queue library is boilerplate.
You used C++'s dequeue, wouldn't that be boilerplate by this bizarre definition? Wouldn't everything?
I DO NOT THINK THAT. Why do you keep implying that my thinking is wrong? That is so arrogant of you.
That's good, I must have misunderstood since you were blaming concurrency bugs on the standard library data structures.
In my case, the queue was used as a "global" kind of object, so no reference counting needed.
I think you might have misunderstood that the reference counting is for anything returned from a data structure so that it can see that something is being used and not modify it. The reference counts of the returned object are actually pointers to the internal reference counts in the data structure, like checking out a library book.
This is not how I would do a queue though and not how the queues I linked work. They copy data in and out and are best used for small data. Large amounts of data can be handled in a different way by a different structure.
where did I imply making "double allocations"?
The C style allocation of structs to pointers then allocation of the underlying data is two allocations and double indirection. This isn't good for multi-threading because allocations have their price, just a heads up.
You're arguing all the time for just buying into stuff as a cargo cult
I don't think so, I've made a lot of stuff that works.
Don't explain basic C++ stuff to me. I understand it.
Well.. we all get bit by standard library assumptions from time to time and need to read the docs, but it just isn't a concurrency problem with the STL.
There's a lot of "abstraction" slop that brings more downsides than upsides.
Claims without evidence unfortunately. The fast concurrent queues I linked are great and using destructors to keep track of reference counts is great. Both are minimal. I would say inserting resource management manually into every function is boilerplate.
Why do you show this to me??? Don't you think I know it?
> But you did blame the STL for concurrency bugs so there must have been something.
I explained the problem at your request: I pointed out that this was a bug I introduced myself, but yes, I blamed it on STL (and its complexity). Again, it was abstractly a "concurrency" problem, and it did manifest when using multiple threads, but the problem was not due to missing mutex nor reference counting. Instead it was because of a kind of iterator invalidation that I had not expected at the time of banging out some shitty iterator code. The uniform iterator abstraction made it arguably way easier to miss.
> You used C++'s dequeue, wouldn't that be boilerplate by this bizarre definition?
yes absolutely, std::deque is a super bad offender, in many ways. I advise against using it. More than against using STL in general, although I don't recommend that either.
> I think you might have misunderstood that the reference counting is for anything returned from a data structure so that it can see that something is being used and not modify it. The reference counts of the returned object are actually pointers to the internal reference counts in the data structure, like checking out a library book.
No I have not misunderstood anything. Again you're coming back to your arrogant pattern. There are many ways to implement reference counting. When doing it manually instead of with e.g. std::shared_ptr, it's quite common to embed the count inside the object, not make it a separate allocation.
> This is not how I would do a queue though and not how the queues I linked work. They copy data in and out and are best used for small data. Large amounts of data can be handled in a different way by a different structure.
There are many types of queues. There are queues that buffer two elements, there are queues that buffer millions of elements. There are queues that get persisted (like a database). There are queues that are ephemeral. There are queues that have multiple produces and/or consumers, there are queues with only a single producer/consumer. There are queues that get locked. There are queues that get accessed with atomics only. There are queues that store elements directly. There are queues that store elements buffered in chunks or packets... "Queue" typically implies FIFO but not always.
> The C style allocation of structs to pointers then allocation of the underlying data is two allocations and double indirection.
But I rarely don't do that. And that's not implied by "C style" at all. And importantly, structure (pointer indirection) doesn't imply allocation strategy.
> Well.. we all get bit by standard library assumptions from time to time and need to read the docs, but it just isn't a concurrency problem with the STL.
I have explained the issue at length. So please stop repeating made-up contradictions.
> Claims without evidence unfortunately. The fast concurrent queues I linked are great and using destructors to keep track of reference counts is great. Both are minimal.
Claims without evidence unfortunately... Except, it's quite evident that there is a lot of code in them and it's hard to find out how anything works because of that. How would I even evaluate if the queue is doing what I need? That queue functionality implemented here should probably be a tenth of that code (!).
> I would say inserting resource management manually into every function is boilerplate.
Good, because I don't do that at all. And I criticize that RAII is a system that sneaks in resource management _implicitly_ everywhere, which is not visible in the source code. That's why I prefer C-style: making it explicit, allowing me to find the optimal structure that avoids unnecessary fluff in the first place.
I don't know what you are upset by this, cppreference is great and it explains the problem you had.
I blamed it on STL (and its complexity).
If you make a simple dequeue with a vector any resize is going to invalidate pointers. It isn't the STL's fault, it's meant to queue simple data for ordering so that you can copy it in and out, not hold on to a pointer to something internal. It isn't meant for actual storage just basic structure.
std::deque is a super bad offender, in many ways. I advise against using it.
You brought it up as something you were using.
No I have not misunderstood anything. Again you're coming back to your arrogant pattern.
It's not arrogant to point out how things work. In this case the pointer to a reference count is pointing to internal data in the data structure so that other threads can see it. This is not the same as shared_ptr which is tracking a reference count of itself.
But I rarely don't do that. And that's not implied by "C style" at all.
So you frequently do that? It's your C style, that's what you showed me and it takes two heap allocations so that a pointer can be returned. If you create a pointer to a struct in a function it can't point to the stack inside the function.
So please stop repeating made-up contradictions.
You did say you got burned by an assumption of pointer invalidation and that was your explanation for how the STL gave you problems with concurrency.
Claims without evidence unfortunately...
There are benchmarks and lots of people use these queues. I've used them and you can use them yourself. It isn't like saying something is bad then not being able to explain it. You and anyone else can and do use these. It is an opinion, but it is backed up by a lot including a great interface.
Good, because I don't do that at all.
Then you probably have memory leaks because you need to call the free functions that you make when you create a data structure.
And I criticize that RAII is a system that sneaks in resource management _implicitly_ everywhere
No, you said that it created implicit program flow, now you're walking that back I guess.
Also it isn't everywhere, it's only where it needs to be, so I don't know why you wouldn't want it there.
> In large codebases you will now have to deal with all three solutions being used, depending on how old the code is.
this is really a problem of "large codebases" and not C++ complexity. If people writing badly organized code.. than.. nothing will help them. Even such string language as Rust.
---
But, I go aside. I can be wrong here, but this is how I see this: - author author worked with C++ early in his career - now his work with something like python or javascript or "promptscript" or don't programming at all - he managed to taste C++ drawbacks - now he reading such article just to see "what's new" in technology he was interested in the past. - he see something what he don't understand from first sight - this looks like a complex and quirky thing => he decide that problem in the language, not in his incompetence in this area
I see such comments very often and usually people don't provide any technical expertise. But just their feeling that "oh, this thing was so complex, now it even more complex".
People will contend themselves with "C++ the good parts", helped by clang-tidy, PVS, MSVC analyse, and move on.