4 ms·
The problem with this is that once you get any deeper than the first call, that helpful "&arg" goes away and it's just "arg". I would argue this sometimes-ther
by cpgxiii 4y ago
The problem with this is that once you get any deeper than the first call, that helpful "&arg" goes away and it's just "arg".
I would argue this sometimes-there single character is incredibly brittle for the claimed benefits, and illustrative of the unwillingness of large parts of the C++ community to use any reasonable tools that would do a better job instead (e.g. very basic syntax highlighting would be far more consistent in indicating mutability).
- kevin_thibedeau 4y ago> that helpful "&arg" goes away and it's just "arg". You still get a reminder it's a pointer by the * and -> notation.
- cpgxiii 4y agoThat only goes so far - without reasonable method names you still don't know if the operation actually mutates the parameter, e.g. in void func(ParamType* param) { ... auto res_1 = param->Operation1(1); auto res_2 = param->Operation2(2); ... } the presence of -> doesn't tell you which method mutates. The only thing that would tell you that would be knowing which method is const.
- dragontamer 4y ago> The problem with this is that once you get any deeper than the first call, that helpful "&arg" goes away and it's just "arg". There's a HUGE difference from a "int& foo" and a "int* foo". ESPECIALLY if I'm doing incredibly sophisticated atomic operations, memory-barrier, and atomic operations (IE: atomic<int>& vs atomic<int>*). Take a guess which one I'm more comfortable passing around. This isn't a case of "param->Operation1(1)" like you're talking about. This is a simple integer being used in some of the most complex, sophisticated, low-level optimizations known to the modern programmer. Knowing when, where, why, and how these things are dereferenced is the entire damn point of memory-barriers and atomics. I want _NO_ surprises, at all. At least, within the context of "C++ Concurrency Models". To be honest, this is the stuff that causes me to run away screaming. But if I'm forced to write code involving the words "compare-and-swap", "atomic", or "barrier", you damn well bet that I'm going to be doing it with "int*" rather than "int&". -------- Every style of code has its place. When dealing with atomics, I definitely prefer pointers over references. If you're running around in object-oriented land, you probably don't care and have modest benefits to the reference. But the minute you start studying "A-B-A" problems and the difference between "load-acquire" vs "load-consume" and the possible memory-orderings that can happen, you suddenly want to become extremely explicit over your dereferencing. -------- "When" did the dereference occur? Is it compatible between Thread#1 and Thread#2? Are there any weird "gotchas" that could occur? Has the dependencies you need been ordered correctly in all threads that are active? If you're unaware of what's going on here, give this a readover. https://en.cppreference.com/w/cpp/atomic/memory_order https://en.cppreference.com/w/cpp/atomic/memory_order I promise you, you'll have some headaches. This is by far the hardest subject in all of low-level programming I've come across. And being more explicit about when the dereferences happen is very useful.
- cpgxiii 4y agoSo, to be clear, you appear to either (a) have a case where there are significant behavior differences between passing by mutable reference versus pointer (and thus _need_ to use a pointer), or (b) are relying on operations to certain types being "atomic" and on the compiler and processor not mangling the code you write without using any explicit concurrency-protecting constructs (e.g. std::atomic<T>) which you believe they would do to a reference argument.
- dragontamer 4y agoHave you read the post in question? Its a good introduction to C++ Concurrency / memory models. atomic<int>* blah; a = *blah; // This is relatively fast. *blah = b; // This is slow: store-release. blah = c; // This is fast, no store-release operation going on. Just a pointer-assignment Anyone dipping down to this level is interested in the performance characteristics of memory barriers. Indeed, the entire point of this exercise is to get faster than std::mutex lock / unlock after all, and squeezing the last ounce of performance out of your code. Indeed, one can argue that when using atomics, the more explicit "store" and "load" functions should be used instead. a = blah->load(); // These two examples are blah->store(b); // arguably the most explicit, preferred choice
- cpgxiii 4y ago> Have you read the post in question? Its a good introduction to C++ Concurrency / memory models. Yes, I have. And yes, I am familiar with the C++ concurrency/memory models discussed here. And, to be clear, nothing in the article is doing anything that couldn't be done just as clearly with std::atomic<bool>& as with std::atomic<bool>*. Obviously, if you actually want to be able to assign to the pointer, then you need the pointer form. > Indeed, one can argue that when using atomics, the more explicit "store" and "load" functions should be used instead. Yes, this is the style I strongly prefer. Of course, here it doesn't matter between a = blah->load(); blah->store(b); or a = blah.load(); blah.store(b); and choosing to use the pointer form is often just introducing nullability where you don't need it.