Live data from Hacker News

The PImpl idiom and the C++26 std:indirect type

mariusbancila.ro

111–120 of 122 posts

Re: The PImpl idiom and the C++26 std:indirect type

#111

Earlier quoted context omitted.

If you aren't, wouldn't that imply that the concurrency problems are with you and not with the standard library?

Why don't you read again what I said instead of continuing to make implications?

You said you had concurrency problems with the STL, but none of it has any intersection with concurrency, that is left to the users. There are no expectations there unless you were using threads, atomics, mutexes etc. and they had bugs.

Re: The PImpl idiom and the C++26 std:indirect type

#112

Earlier quoted context omitted.

Why don't you read again what I said instead of continuing to make implications?

You said you had concurrency problems with the STL, but none of it has any intersection with concurrency, that is left to the users. There are no expectations there unless you were using threads, atomics, mutexes etc. and they had bugs.

Try harder, Sherlock. You're very close to proving that I assumed the STL was "thread safe". You've almost got me.

Re: The PImpl idiom and the C++26 std:indirect type

#113

Earlier quoted context omitted.

You said you had concurrency problems with the STL, but none of it has any intersection with concurrency, that is left to the users. There are no expectations there unless you were using threads, atomics, mutexes etc. and they had bugs.

Try harder, Sherlock. You're very close to proving that I assumed the STL was "thread safe". You've almost got me.

I'm not trying to prove anything, I'm asking why you "had concurrency problems from using the STL" when the STL doesn't have anything to do with concurrency.

Re: The PImpl idiom and the C++26 std:indirect type

#114

Earlier quoted context omitted.

Try harder, Sherlock. You're very close to proving that I assumed the STL was "thread safe". You've almost got me.

I'm not trying to prove anything, I'm asking why you "had concurrency problems from using the STL" when the STL doesn't have anything to do with concurrency.

You have repeatedly proven, and continue to do so, also by way of your exchanges with other commenters, that you're not asking out of curiosity. After all the previous comments we've exchanged, your line of asking was, "Which part of the STL are you expecting to be thread safe?" i.e. you were continuing to assume that I was somehow naive or uneducated. I did not assume anything to be thread safe in the way you imply (I used a simple mutex based approach to protect accesses).

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.

Re: The PImpl idiom and the C++26 std:indirect type

#115

Earlier quoted context omitted.

I'm not trying to prove anything, I'm asking why you "had concurrency problems from using the STL" when the STL doesn't have anything to do with concurrency.

You have repeatedly proven, and continue to do so, also by way of your exchanges with other commenters, that you're not asking out of curiosity. After all the previous comments we've exchanged, your line of asking was, "Which part of the STL are you expecting to be thread safe?" i.e. you were continuing to assume that I was somehow naive or uneducated. I did not assume anything to be thread safe in the way you imply…

To which I'm going to answer, one of the bugs I hit was due to unexpected invalidation of a std::deque iterator.

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:

https://github.com/cameron314/concurrentqueue

Re: The PImpl idiom and the C++26 std:indirect type

#116

Earlier quoted context omitted.

You have repeatedly proven, and continue to do so, also by way of your exchanges with other commenters, that you're not asking out of curiosity. After all the previous comments we've exchanged, your line of asking was, "Which part of the STL are you expecting to be thread safe?" i.e. you were continuing to assume that I was somehow naive or uneducated. I did not assume anything to be thread safe in the way you imply…

To which I'm going to answer, one of the bugs I hit was due to unexpected invalidation of a std::deque iterator. 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 fr…

Serious question, are you an AI programmed to be annoying?

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

Re: The PImpl idiom and the C++26 std:indirect type

#117

Earlier quoted context omitted.

To which I'm going to answer, one of the bugs I hit was due to unexpected invalidation of a std::deque iterator. 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 fr…

Serious question, are you an AI programmed to be annoying? > 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 b…

are you an AI programmed to be annoying?

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.

Re: The PImpl idiom and the C++26 std:indirect type

#118

Earlier quoted context omitted.

Serious question, are you an AI programmed to be annoying? > 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 b…

are you an AI programmed to be annoying? 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 b…

> I gave you great information

"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!

Re: The PImpl idiom and the C++26 std:indirect type

#119

Earlier quoted context omitted.

To which I'm going to answer, one of the bugs I hit was due to unexpected invalidation of a std::deque iterator. 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 fr…

Serious question, are you an AI programmed to be annoying? > 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 b…

Actually, not half a dozen, more like hundreds if you count all the tiny ad-hoc stuff too (many of them similar or the same).

Re: The PImpl idiom and the C++26 std:indirect type

#120

Earlier quoted context omitted.

are you an AI programmed to be annoying? 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 b…

> I gave you great information "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 un…

In any case, nothing I hadn't already known.

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.

Post reply on HN