New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expand C.9: (Minimize exposure of members) for member functions #1889
Comments
So you're advocating the PIMPL method - that's useful for hard ABI requirements; but it's not the only method. For instance, you could achieve the same effect by having a public interface that only defines the public side and then a private implementation that derives from the public interface. This is actually cleaner than the PIMPL method, which usually ends up littering the code with things like P & Q pointers for which portion (public or private) of the object you want to work on; it also makes it easier for debuggers, it just requires that you separate the code and headers a bit more:
Done right you have very easy source compatibility across versions and can easily change out the underlying implementation or even relink without having to worry about what is using the public interface. So what's my point here as relevant to this repo? There's multiple paths that are equally viable that solve the same problem. Which one you choose is really based on how you want to work, etc. Therefore this is probably something that shouldn't be in the guidelines, at least not unless you want to have a section discussing all the different equally viable methods which if I'm not mistaken is beyond the scope of these guidelines. |
I don't see what this has to do with pimpl, as there is no pointer (and also no dynamic allocation). That being said: using local helper functions instead of private member functions is certainly a valuable tool, but a general ban on private memberfunctions seems far too strict for me. At least in a non-module world, where a lot of public functions are implemented in the header anyway (templates, header only libraries or just in order to facilitate inlining) and consequently the helpers must be available in the header anyway. |
The two namespaces have to integrate in some manner. Whether that's using the Pointer method like PIMPL does or a similar non-pointer method, it's still the same basic pattern of switching between internal objects based on what part of the object (public vs private) you're working on.
I do agree. Personally, I use a mix of public, protected, and private methods - even friending on a rare occassion - and find them all useful to use. Not everything should be public; nor should every non-public thing be protected. |
There is no internal object. The op is talking about private functions not private data. |
No, look at my examples again, there is no pointer to any implementation, just a smaller header file that does not expose some implementation details, but also does not change the semantic of the class (if part of class hierarchy, if it has values/move/copy semantic, ...) nor adds any indirection (a pointer to some implementation) or increases memory usage (no additional allocation) PIMPL is useful for hiding (mutable) data, I'm talking about functions. The point you make have little to do with the proposed addition, and the suggested approach requires much more code, indirections, and the final class has probably not value semantic, or requires additional memory.
"Ban" is a big word, C.9 does not currently ban every class that does not use PIMPL, or has public member functions. In some cases (templates, friends, constexpr, header-only library, ... and as you mentioned, aggressive inlining) it is not even possible or desirable to move private functions to implementation files. |
PIMPL is one method. It doesn't necessarily have to do with data; it has to do with functional separation between public and private APIs. PIMPL itself uses pointers (typically name P & Q); but you could achieve the same result with a static allocation of another class object - so you don't necessarily have to dynamically allocate memory to achieve the goal. Conversely, I also mentioned using separate private and public headers for different definitions, abstract classes, inheritance, etc - another way to skin the hypothetical cat. This also doesn't necessitate dynamic allocation of memory. The method mentioned in the original message discusses using anonymous namespaces to achieve something like (or a variation on) PIMPL (not called out, but the same effect in the end). My point still stands, however, that there are multiple, valid approaches to solving the problem of hiding some implementation details - method and/or data, take your pick; and I'm not sure the guidelines should be favoring one method or another. Each has its trade-offs, usages, etc. I'll be quiet now, and let the editors make their call. |
Well, if there is no data... then probably there does not need to be any pointer to anything, or am I missing something?
Nitpicking: In C this "pattern" (I'm not even sure it should be called pattern as it is such an obvious property, it's the main reason we have header and implementation files) is (in my experience) followed more strictly.
The guidelines have PIMPL as concrete suggestion. PIMPL is for solving the issue that a class normally needs it member variables to be exposed, does not matter if public or private, and PIMPL is a technique for avoiding such exposure when it is problematic. Also this "technique"/"pattern" does not rule PIMP or your suggestion out, and viceversa, your suggestion and PIMP do not rule this technique out. |
An alternative pattern that I've come to love is what some people call an "Impl pattern". The basic idea is that you declare a dummy "Impl" class in the scope of your class thus giving it access to private variables. And then write static member functions i.e. // Header
class Foo {
public:
void foo();
private:
struct Impl;
int data;
};
// Implementation
struct Foo::Impl {
static std::optional<int> fun(Foo& self) {
return self.data == 42 ? std::nullopt : self.data;
}
// Other private functions...
};
void Foo::foo() {
const auto r = Impl::fun(*this);
// ...
} This pattern requires some boilerplate code, namely having to type Just my 2c |
Thanks for the suggestion, I'm aware of this pattern. |
No, the suggested wording doesn't rule this pattern out. However, I agree with Mike in that this note seems a bit too situational to be included in the guideline. I guess you could put it under "motivated recommendation" but historically that's not how people interpret C++ Core Guidelines, unfortunately. Personally I don't use this pattern to hide implementation details per se but rather to avoid the unnecessary recompilation on my potato laptop. I don't really have an opinion on whether private member functions should or should not be exposed. Maybe the note should say something along the lines of "hey if you don't want to expose private member functions you can do this or that"? |
I was mostly referring to your suggested enforcement section. |
I see.
And this is one very good reason why I believe it should be part of the guidelines. Why would you want to pessimize your experience? |
But thats just it: Stated as is, with the only exception of virtual functions, I can't think of a single project where I would apply it and I can't believe I'm the exception in this respect. At the very least I would like to see this check limted to header files and have an exemption for constexpr member functions and member functions of templates. AFAIK (I'm not a CG Maintainer) the CG are explicitly not meant to be a "pick-and-choose" collection of guidelines (although they imho sometimes feel like it). By default you should be able to apply all checks to a (new) project and not have to go through each and every one of the guidelines and have to decide (worst case by committee) "Is this useful for my project? Is this? And that?". Of course there will alsways be project specific list of checks that wil be disabled, but imho the guidelines shoul strive to be written in a way that those are as small as possible. Just to be clear: Contrary to some other posters in this thread, I'm not opposed to the recommendation in the note itself. "Avoid" and "Consider" are sufficiently strong qualifiers to make it clear this is not a hard universal rule. And contrary to some of the alternatives that have been mentioned here, the suggested alternative is simple and straight forward to apply and understand. |
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c9-minimize-exposure-of-members states that it is a good thing to hide implementation details, and explains that using a private member variable is, without too much effort, too many headaches and overhead, the simplest and most straight-forward solution. I would like to have a guideline for functions too, but the guideline for data is not the simplest and most straight-forward solution for functions. Just like C.9 says in a note that protected data is bad, I would be completely satisfied to have a note (not a separate guideline) about it. |
@fekir : As I said:
I'm just against the enforcment part in the original post. Alternative would be something like:
|
Looks good to me |
The same point obviously holds for free functions and classes; do not put them in the header file if those are implementation details unused by the users (unless constexpr, blah blah, same list of exceptions). Fortunately with free function and classes I do not see this anti-pattern that often. |
A talk that basically proposes the same I wrote (from 2017) In particular Encapsulation (after ~5 min), but I think the whole talk is worth a watch |
Editors call: The problem seems rare and specialized. And it's not clear why an anonymous namespace is needed, just defining the function out of line removes the dependency (just using the type's name in a signature doesn't require a definition). |
@hsutter A couple of question to understand the process better
Why not ask before closing? The anonymous namespace tells the reader that the function is private/that it is used only in the given translation unit, and it avoid exporting it by accident (unlike with MSVC on Windows, with GCC and Clang all symbols are exported by default)
Is there some data that indicates that private (non |
|
My two cents as a random developer. The overall intention of this change seems valid and similar to the current rule on data. If you don't need to expose something, don't. Are the maintainers against the spirit of the change? Or the change itself? Would a better-worded request be approved (something that doesn't mention anonymous namespacing)? |
This is going off-topic, as the anonymous namespace is just an implementation detail... but the anonymous namespace should be the preferred way ( See also the corresponding core guideline: What "lots of rework" does an anonymous namespace imply versus a static function? (But maybe we can move the discussion somewhere else, as it is not relevant for this issue?)
vs
|
@pele1410 re "Would a better-worded request be approved" - pull requests are looked at first and pass more often, but it has to be convincing that it's addressing a problem dangerous enough or common enough to be a "core" guideline. @fekir "guideline for member functions seems as justified as a guideline for member variables." we already have that guideline: C.4: Make a function a member only if it needs direct access to the representation of a class |
Thank for pointing to C.4, but it does not address exactly the same issue, (or at least it seem to me). The motivation for C.4 is that some function can change a given object with a subset of the provided interface, thus there is no need to give it access to all implementation details. A private (non-static) member function normally touches (reads or writes) some (or all) member data. But in practice (excluded the cases I mentioned), this is not necessary. C.9 seems to me a better place where to add such guideline because it talks about private members (functions or data) but provides concrete examples only for data. I would also be happy if we can expand C.4. Also C.4 is incompatible with the pattern used by @coffee-lord, which is compatible (except for the anonymous namespace) for the proposed guideline (enforcement would need rewording). |
Since (and only since) you asked...
Often in this case if you want you need to reference Further, it can then be easy to promote the function to public (just remove the static declaration and promote the function definition to a public header) and again, no namespace changes required - just how and where the function is declared (forward declaration). Using the anonymous or Don't get me wrong - I like C++ namespaces and use them extensively. But they do have their drawbacks too. Sometimes it's simpler to just not use them at all. |
Currently C.9 explains mainly how to handle member variables, but says nothing about member function.
In particular I would like to add something like
And as example something like
header file (before)
impl file (before)
and
header file (after)
impl file (after)
and as enforcement "flag private non-virtual member functions"
Benefits:
fun
is dead code or not.The text was updated successfully, but these errors were encountered: