I used to carefully design my C++ code with public / private / protected / friend (and the pointer-to-impl pattern for "compile time private") but a few years ago I started doing everything all-public ala python (struct instead of class by default) with occasionally an m_ prefix for "intended to be private", and I've never looked back. It's been great, made my programming life much easier.
This badge thing is clever, but seems like yet more accidental/unnecessary complexity on top of the already-probably-unnecessary private/protected/friend - as requirements change you now have this extra layer of access-control cruft to reason about, that you probably wouldn't even miss if it didn't exist.
Not to get too ranty, and no disrespect to Andreas, but the C++ culture seems to thrive in unnecessary / accidental complexity, deep template hacks and the like that add friction to refactoring, obfuscate underlying logic so you miss opportunities to simplify, and turn straightforward programming problems into brain-bending template puzzles.
OOP also; I've seen so many crazy class architectures that could just be a handful of plain-old-functions, complex virutal "type families" that could just be a TypeOfThingThisIs enum with some if statements in the respective functions, etc.
I don't know the Serenity codebase and use-case very well, and perhaps this kind of thing has a place in large libraries, but honestly even then I'd lean towards just making everything simple & public and use a prefix when needed, or describe appropriate usage in documentation. Seems to work in the python world (minus a few outlier codebases that go OOP-crazy).
Everything public versions poorly when you can't update all clients of your code after you change your implementation. It increases coupling in ways you can't control.
It also doesn't work very well with code completion - implementation methods get mixed up with surface API - and usability by third parties suffers.
If you're on a team of one, and you own the project for its whole lifetime, then you're fine.
Admittedly my use case is generally projects that are under my control.
But in the case of say an open source library, how likely are clients going to update their code with each new release of the library? Typical case is to do it from time to time, especially with compiled code / shared libs, and expect a few incompatibilities to sort out and fix when you do.
In a fast evolving large team project, seems it depends on how well the boundaries of the subsystems are worked out. Misusing supposed-to-be-private members is one minor possibility amidst many challenges.
In larger codebases with many developers, attention to the API is required in order to be able to maintain code without breaking clients.
The main downside of Badge<T> is that it comes with a performance penalty and ABI change for something that should be statically deducible (empty struct is one byte in C++).
The example also shows a call to a different translation unit. There's no code for passing the empty struct. There's also none for handling it, as you can see from the assembly output.
The one byte rule does nothing here, because its value is undefined and its address not observable.
edit: if you find a case where an empty struct parameter causes code to be emitted, let's consider filing bugs.
Oops, I did miss that, but this seems to be compiler or calling convention specific. x86_64 gcc 9.1, linked, shows no difference. However arm64 gcc 8.2 and x86-64 clang show a difference.
Don't you have to have pretty high performance needs for this class to make a performance difference? Unless it's called multiple thousand times per second I don't see it making much of a difference.
You can make register_device() an inline function that just calls a do_register_device() private function without the badge. That way you don't have to pay for the empty class.
I'm not sure I follow... The motivation for Badge is to have methods on VFS that only Device can call, without exposing VFS's internals to Device. Without Badge, what prevents non-Device functions from calling register_device()?
I guess the inline function would require a Badge and the private member function would not. So an inline constructed but never used Badge is even more likely to be completely optimized away
> OOP also; I've seen so many crazy class architectures that could just be a handful of plain-old-functions,
also, tons of "generic" numerical code with templates everywhere, where the only two possible instantiations are "float" and "double". Some people do really like to add useless complexity everywhere.
Eh... I have seen the same in Rust. People write tests with i32. i32 implements the Copy trait which means your can blindly clone it all over the place. When you try using some libraries with String or anything else they don't work.
Not only is it needlessly complex, but untested generic code is a minefield of subtle future bugs ... "oh, the function is there and tested, so it should work" ... well, not in this particular combination of the completely unrestricted generic parameter set.
I don't understand why you have been downvoted for asking "why m_?". Is everybody on here supposed to know everything about everything, else you get downvoted into grey unreadability?
Personally, I think that bashing/penalizing ignorance (and I mean the word ignorance literally, as being uninformed about a topic, no offense here whatsoever) is one of the lowest forms of abuse. It says much more about the downvoter than it does about the downvoted. And this not even considering the fact that searching online for "m_" isn't going to help you answer your own question.
If everybody just downvoted the questions they deem unworthy of an answer because "you are supposed to know this", there would be no questions answered.
I fail to see the "complaining" side in all of this. I interpreted the question as "what's the meaning behind 'm_'?". Until I read your comment, it didn't occur to me to see it in a complaining light, also because I imagine the question would have been worded differently in that case (e.g. "why m_ instead of <something_else>?").
And judging by other comments to the question, I wasn't alone, luckily.
This is the often case where some HN downvotes you to heck and the next hour bunch shoots you up to the moon. There is pretty much no predictability and it all depends on whether your responder makes a good case.
OP's approach works if all dudes involved read the code before chaning it and stay off private parts even if they aren't guarded by the language constructs.
That's not how software development works in e.g. companies where multiple people use the software (framework, libraries, API's) at different levels/roles.
For example, if we develop some framework to be used at multiple sites in the company, we can be 100% sure that at some point someone will assume that anything marked 'public' in the API is fair game to use (which IMO is a reasonable perspective). If they start using internal API's or depend on class internals that are only public just because the language did not provide us with a reasonable way to hide them, at some point we will change them and working systems could break elsewhere. In a similar fashion, misuse of private API's by violating preconditions that are completely opaque to users of your (intended) public API can lead to the worst kinds of bugs and unpredictable runtime failures.
Software development always seems so simple and pragmatic if you don't need to care about other people you have no control over.
... in your setup. All shops are different. To each their own. There's no private/protected scaffolding in the Linux kernel to give the most obvious example.
Well last time I checked the Linux kernel was C and not C++, so that might explain why ;-).
I don't know anything about kernel programming, but I assume that there's some very, very strict conventions about how the various parts of the Linux kernel interact, and nothing gets accepted for merging unless you follow them. This would be similar to imposing scope/visibility constraints at the language level, but considering C doesn't have anything for that, an extremely strict development process is the best you can do.
emm...
All we could say is that,
kernel programmers'/reviewers' technical background is much higher/better than average python programmers'/reviewers'.
Seen too many times these "Mr/Mrs. More or less" engineers simply modify the internal states as short cuts to compensate real engineering.
Equally being too defensive and over-complicating code in case a "bad" developer makes a breaking change often leads to code that is worse in the long term.
So it's an implicit contract based on syntax vs explicit?
It's a user/m_ prefix vs compiler/private. Im not sure if former is better. It's a strong trade off imho.
In my experience relying on people reading code and agreeing on imlicit contracts does not scale beyond 5 people, but maybe I've been mistreated by life.
No, not really. It works quite well for large projects too. Just look at any open source that is C-based.
The only mechanism for safe-guarding access to private parts is using privately defined structs that are passed around in a form of opaque pointers. Some project use it (PGPphone did, the original SSH did, etc.), some don't, e.g. BSD and Linux kernels.
It's really more of a matter of developers exercising some basic thought when using other people's code. And it just happens so that there's more of them outside of the C++ group than in it.
I come from an embedded development environment, and for me simplicity is everything. But even that, I think the approach of an "all public" class is a little bit risky, and can be a pain to debug if accidentally you change an internal state.
If a class is a black box, when you put your hand inside it, it becomes the "pain box" from the Bene Gesserit.
This style of development obviously will not scale to large, long-term C++ projects with many developers, as anyone who has worked on such projects knows all too well.
No disrespect to you, QuadrupleA, do whatever makes you the most productive, but I wouldn't want to work together with you in this way :)
Over a long enough time, every API will be called in every possible way. If an API misbehaves, some code somewhere will begin to rely on the specific kind of misbehavior. This is just the reality of publishing API's, so you deal with it and fix bugs as they are discovered.
If you make your entire class definition public in a large project, you will eventually have code poking at the internals of your class. I've seen this countless times, it's some kind of inevitable natural thing.
Developer scalability:
You know everything about how your code works, but I don't. A well-defined API with clear access rules gives me a fair chance to avoid using your class in ways you didn't intend.
In small-enough teams, or with small-enough projects, it's possible for everyone to understand (mostly) everything, but there's some magic number of concurrent developers where that privilege ends.
> I started doing everything all-public ala python (struct instead of class by default) with occasionally an m_ prefix for "intended to be private", and I've never looked back.
This is the sort of accidental complexity that access specifiers eliminated, but somehow you managed to reintroduce by trying to reinvent the wheel.
The 'm_' prefix only means "private member variable, don'access it" to you and you alone, and anyone who will stumble on your code will not be aware or obligated to follow your personal style. That would not be the case.if you simply used a basic feature of C++ that's been available since the time of "C with classes".
To me, you're the epitome of the main source of C++'s problems: programmers who failed to learn the basics of a language and instead decided to develop convoluted hacks to circumvent proper use of the language because they believe their new discovery is clever although in reality they only managed to needlessly add complexity to a code base while adding brittle and bug-prone code.
What else? Do away with memory deallocation because there's nothing of the sort in Python and your C++ code is packed with segfaults?
And by the way, the pimpl pattern is used to provide libraries a stable ABI in spite of what change might be done to the implementation. It is not a fad or a clever-looking trick.
Python has a well-established convention of _underscore for intended-to-be-private members, with no enforcement, and it works fine. Just document your conventions. The horrors of some other programmer using it wrong seem overblown to me.
In my IDE in c++ I can quickly refactor m_ to non-m to make it "semantically public" without having to edit header files, change struct packing order / ABI, etc. And most of the time I don't even bother with m_. It's one less thing to think about. I honestly don't think access specifiers are worth wasting mental cpu cycles on in most projects.
Besides, in c++ people can always cast your intricately-protected object to a void pointer and start manipulating it nefarioisly anyway. Just document and communicate, and write simple straightforward code.
I used pimpl at the time for faster recompiles when changing "private" stuff. Better build tools got me around that problem personally, but I'm sure it has it's uses.
Those access modifiers are there for a reason and they work very well in not just C++ in many other object oriented languages like C# and Java. So, using Python to justify that style of coding is a far cry from anything that is reasonable. If it work for you that's fine, but I don't think it's a good idea generally.
> Python has a well-established convention of _underscore for intended-to-be-private members, with no enforcement, and it works fine.
Python does not have access modifiers. C++ does. It makes absolutely no sense to write Python code in C++ while entirely oblivious to basic, age-old C++ features just because your background lies somewhere else and you failed or refused to learn even the basics.
And you know what actually works instead of just "working fine"? Having the compiler throw an error if a careless programmer tries to access private variables.
> In my IDE in c++ I can quickly refactor m_ to non-m to make it "semantically public" without having to edit header files, change struct packing order / ABI, etc.
Don't you understand that's completely irrelevant? Just set the private member as private and it does not matter at all which naming convention you follow. Don't you understand that having to do nothing is better than expecting team members to be prescient about your convoluted and absurd naming convention used to avoid best practices?
> Besides, in c++ people can always cast your intricately-protected object to a void pointer and start manipulating it nefarioisly anyway.
Don't you understand that't entirely irrelevant? You're trying to argue that it's ok to ignore best practices such as using member access specifyers, which actually get the compiler to validate the code, if you stick with naming conventions, but arguing that it's technically possible to circumvent compiler checks under some specific circumstances is an absurd statement. Think about it: if you feel that enforcing a coding style is enough to enforce private member access then don't you agree that the same code review that is supposed to enforce your personal style can quite easily catch your hypothetical casts?
You keep accusing me of "failing to understand" or "refusing to learn" the "best practices" of using access specifiers.
I understand them well, and I've experimented with their use in my own projects, and decided they're a minor complexity I don't need to bother with. And that decision has served me well.
Dogmatic adherence to "best practices" is silly, even when people can agree on what they are. Learn them, yes, evaluate them carefully, but ultimately choose what works best for your constraints. Including what your team's conventions are.
The programmers I've seen taking most about "best practices" generally don't think very critically about their approaches and go with dogmatic answers, often tying up their codebases in ridiculous complexity. E.g. the "best practice" now to make a website is a React.js SPA, and now every site out there takes 20 seconds to load with 10 little spinner icons while all the HTTP requests go out to their microservices on docker clusters, and everything requires a hundred-library-deep build stack.
I strongly agree with your sentiment that 'best practices' act as a replacement for critical thought, and lead to messy, overcomplicated and flawed architectures. Every "best practice" would be best delivered with a criteria list of when it's actually applicable, and a shortcomings list of situations where its not.
> and now every site out there takes 20 seconds to load with 10 little spinner icons while all the HTTP requests go out to their microservices on docker clusters, and everything requires a hundred-library-deep build stack
That’s just the cost of progress. Gotta break a few eggs to make an omelet. No pain, no gain. There are always casualties in war. Get with the times, grandpa.
This badge thing is clever, but seems like yet more accidental/unnecessary complexity on top of the already-probably-unnecessary private/protected/friend - as requirements change you now have this extra layer of access-control cruft to reason about, that you probably wouldn't even miss if it didn't exist.
Not to get too ranty, and no disrespect to Andreas, but the C++ culture seems to thrive in unnecessary / accidental complexity, deep template hacks and the like that add friction to refactoring, obfuscate underlying logic so you miss opportunities to simplify, and turn straightforward programming problems into brain-bending template puzzles.
OOP also; I've seen so many crazy class architectures that could just be a handful of plain-old-functions, complex virutal "type families" that could just be a TypeOfThingThisIs enum with some if statements in the respective functions, etc.
I don't know the Serenity codebase and use-case very well, and perhaps this kind of thing has a place in large libraries, but honestly even then I'd lean towards just making everything simple & public and use a prefix when needed, or describe appropriate usage in documentation. Seems to work in the python world (minus a few outlier codebases that go OOP-crazy).