C++: Why must private functions be declared?

12,692

Solution 1

I asked why private functions had to be declared at all, as they don't add anything (neither object size nor vtable entry) for other translation units to know

If you think about it, this is similar to declaring some functions static in a file. It's not visible from the outside, but it is important for the compiler itself. The compiler wants to know the signature of the function before it can use it. That's why you declare functions in the first place. Remember that C++ compilers are one pass, which means everything has to be declared before it is used.1

From the programmer's point of view, declaring private functions is still not completely useless. Imagine 2 classes, one of which is the friend of the other. The friendzoned class2 would need to know how the privates of that class look like, (This discussion is getting weird) otherwise they can't use it.

As to why exactly C++ was designed in this way, I would first say there is the historical reason: the fact that you can't slice a struct in C, was adopted by C++ so you can't slice a class (and adopted by other languages branched from C++, too). I'd also guess that it's about simplicity: Imagine how difficult it would be to devise a method of compilation in which you can split the class among different header files, let your source files know about it, and prevent others from adding stuff to your class.

A final note is that, private functions can affect vtable size. That is, if they are virtual.


1 Actually not entirely. If you have inline functions in the class, they can refer to functions later defined in the same class. But probably the idea started from single pass and this exception later added to it.

2 It's inlined member functions in particular.

Solution 2

You have to declare all members in the definition of the class itself so that the compiler knows which functions are allowed to be members. Otherwise, a second programmer could (accidentally?) come along and add members, make mistakes, and violate your object's guarantees, causing undefined behavior and/or random crashes.

Solution 3

There's a combination of concerns, but:

  • C++ doesn't let you re-open a class to declare new members in it after its initial definition.
  • C++ doesn't let you have different definitions of a class in different translation units that combine to form a program.

Therefore:

  • Any private member functions that the .cpp file wants declared in the class need to be defined in the .h file, which every user of the class sees too.

From the POV of practical binary compatibility: as David says in a comment, private virtual functions affect the size and layout of the vtable of this class and any classes that use it as a base. So the compiler needs to know about them even when compiling code that can't call them.

Could C++ have been invented differently, to allow the .cpp file to reopen the class and add certain kinds of additional member functions, with the implementation required to arrange that this doesn't break binary compatibility? Could the one definition rule be relaxed, to allow definitions that differ in certain ways? For example, static member functions and non-virtual non-static member functions.

Probably yes to both. I don't think there's any technical obstacle, although the current ODR is very strict about what makes a definition "different" (and hence is very generous to implementations in allowing binary incompatibilities between very similar-looking definitions). I think the text to introduce this kind of exception to the rule would be complex.

Ultimately it might come down to, "the designers wanted it that way", or it might be that someone tried it and encountered an obstacle that I haven't thought of.

Solution 4

The access level does not affect visibility. Private functions are visible to external code and may be selected by overload resolution (which would then result in an access violoation error):

class A {
    void F(int i) {}
public:
    void F(unsigned i) {}
};

int main() {
    A a;
    a.F(1); // error, void A::F(int) is private
}

Imagine the confusion when this works:

class A {
public:
    void F(unsigned i) {}
};

int main() {
    A a;
    a.F(1);
}

// add private F overload to A
void A::F(int i) {}

But changing it to the first code causes overload resolution to select a different function. And what about the following example?

class A {
public:
    void F(unsigned i) {}
};

// add private F overload to A
void A::F(int i) {}

int main() {
    A a;
    a.F(1);
}

Or here's another example of this going wrong:

// A.h
class A {
public:
    void g() { f(1); }
    void f(unsigned);
};

// A_private_interface.h
class A;
void A::f(int);

// A.cpp
#include "A_private_interface.h"
#include "A.h"

void A::f(int) {}
void A::f(unsigned) {}

// main.cpp
#include "A.h"

int main() {
    A().g();
}

Solution 5

One reason is that in C++ friends can access your privates. For friends to access them, friends have to know about them.

Share:
12,692
Ancurio
Author by

Ancurio

Student who messes around with code at work and in his free time for fun. Working towards a B.S. in CS.

Updated on June 03, 2022

Comments

  • Ancurio
    Ancurio almost 2 years

    Why do classes in C++ have to declare their private functions? Has it actual technical reasons (what is its role at compile time) or is it simply for consistency's sake?

  • Mooing Duck
    Mooing Duck almost 12 years
    yes, but why does the spec say they must be declared? You didnt answer the question. There s already rules saying functions must be declared before calling, so that's not it. (the namespace tip is a good one though)
  • Ancurio
    Ancurio almost 12 years
    Yeah, I'm used to hiding implementation in C by using static C functions, that's why I'd have expected to simply declare (before any implementation) private functions inside the cpp apart from the rest, just like static declarations in C
  • Shahbaz
    Shahbaz almost 12 years
    @Ancurio, even though what you say absolutely makes sense, unfortunately there is no support from C++ to slice a class definition in two.
  • Nordic Mainframe
    Nordic Mainframe almost 12 years
    That's not want Ancurio wants to know. He wonders why we can't (some) keep private functions as a secret in the first place. You can't call a function that you don't know about.
  • nate_weldon
    nate_weldon almost 12 years
    the compiler needs to know that its private. so it can kick out a compile time warning when someone else try to call it...nothing is ever really a secret
  • Mooing Duck
    Mooing Duck almost 12 years
    Having the signatures declared before usage is only vaguely related to why they have to be in the class itself. The friendzoned class doesn't need to know the privates of the other, it's member function definitions need to know the privates. That can also come later/outside the class. And there are interesting things you can do with templates which could be said to slice a class definition in two. Which reminds me, class definitions with inline functions are two-pass, not one pass. Thats why you can use members/functions in a member function definition before that member is declared.
  • Ancurio
    Ancurio almost 12 years
    @nate_weldon that's true, but if private non-virtual functions weren't declared in a public header file (and therefore documentation) in the first place, no one would attempt to call it. Except maybe for myself... in which case that warning (isn't it an error actually?) does make sense.
  • GManNickG
    GManNickG almost 12 years
    Eh, this seems to only point out that overloading rules would need to change.
  • Shahbaz
    Shahbaz almost 12 years
    @MooingDuck, indeed, I am aware that the pass on the class declaration itself is not one-pass. However, this still applies since most of the times you define the functions outside the class, after the class has been declared. Like I said in a comment above, it would have been nice if you could put the declarations of private members/functions in an internal header file, but there is no direct support for it. (You can always have a void *internal;, but that's just a workaround).
  • nate_weldon
    nate_weldon almost 12 years
    accept for friends ...because friends show each other their privates
  • nate_weldon
    nate_weldon almost 12 years
    this also had to deal with how things are cast to other types
  • nate_weldon
    nate_weldon almost 12 years
    from the standard 3 [ Note: A member of a private base class might be inaccessible as an inherited member name, but accessible directly. Because of the rules on pointer conversions (4.10) and explicit casts (5.4), a conversion from a pointer to a derived class to a pointer to an inaccessible base class might be ill-formed if an implicit conversion is used, but well-formed if an explicit cast is used.
  • JoeFish
    JoeFish almost 12 years
    I think you just described Python classes =)
  • Mooing Duck
    Mooing Duck almost 12 years
    @Shahbaz: So, as the OP asked, why is there no support for it?
  • bames53
    bames53 almost 12 years
    @GManNickG To take visibility into account and not select inaccessible methods? C++ has good reason for the way it works. For example: stackoverflow.com/questions/644461/…
  • Steve Jessop
    Steve Jessop almost 12 years
    I don't think the overloading rules would need to take accessibility into account. They'd probably just have to be defined to only take into account member functions which are declared before the point of use -- so if your private member function is in the class definition then it stops a.F(1) compiling in client code, and if it's added only in A.cpp then it doesn't affect client code but does affect the .cpp. This is already how namespaces work (if you add more overloads, they might get selected). There's some complexity required, for example what happens with two-phase lookup.
  • Shahbaz
    Shahbaz almost 12 years
    @MooingDuck, besides the historical reason (the fact that you can't slice a struct in C, was adopted by C++ so you can't slice a class (and adopted by other languages branched from C++, too)), I'd guess it's about simplicity. Imagine how difficult it would be to devise a plan so you can split the class among different header files, let everyone know about it, and prevent others from adding stuff to your class.
  • Xeo
    Xeo almost 12 years
    @Neal: Well... that's the friggin purpose of friends.
  • Mooing Duck
    Mooing Duck almost 12 years
    @nate_weldon: useful information belongs in answers not comments. Comments can and are deleted without warning.
  • Xeo
    Xeo almost 12 years
    The current modules proposals have solutions to the privacy issue, simply because they add another "layer" or "room" of visibility: The class itself, the module, the "outer code". private will then simply not leak outside the module definition, and the compiler can still know about everything.
  • bames53
    bames53 almost 12 years
    @SteveJessop Of course that could be done, but I think it'd be quite a significant change. For example, currently the scope for class members is the entire class, even before the class member is declared. I don't think the complexity is warranted because it only solves one special case of the underlying problem that is the C++ compilation model, and there are already workarounds that solve that special case.
  • Steve Jessop
    Steve Jessop almost 12 years
    I agree that the change isn't needed, I'm just imagining the confusion as instructed, and I don't think it's all that confusing. Class members declared in the class definition could of course continue to be visible anywhere in the class as before. Members added by this hypothetical new mechanism would have different visibility, since that's the entire purpose of the new mechanism.
  • bames53
    bames53 almost 12 years
    @SteveJessop I don't know, that sounds pretty bad to me. For example it's adding new opportunities to violate the ODR without anything obviously being defined twice. Anyway, don't imagine your own confusion, imagine the confusion of someone that isn't already perfectly at home with things like SFINAE and two-phase name lookup.
  • curiousguy
    curiousguy almost 12 years
    "C++ doesn't let you re-open a class to declare new members in it after its initial definition." That is not correct: 14.7.3 Explicit specialization [temp.expl.spec] "An explicit specialization of any of the following: (...) — member class template of a class or class template — member function template of a class or class template can be declared by a declaration (...)" "An explicit specialization shall be declared in a namespace enclosing the specialized template."
  • Steve Jessop
    Steve Jessop almost 12 years
    @curiousguy: not sure I follow you. I don't think that a specialization of a member template is a "new member", since the template is the member. I may be wrong. If I am wrong then of course templates are an exception to what I was saying, since as well as specializing member templates outside the class you can of course also instantiate member templates outside the class. In neither case do I think that the class has been "re-opened", and I don't think it helps the questioner.
  • curiousguy
    curiousguy almost 12 years
    @SteveJessop A template definition describes a potentially infinite set of specialisations. You can view a function template declaration as the "potential" declaration of an infinite set of functions, and a member template declaration as the "potential" declaration of an infinite set of members. You can count a new member template explicit specialisation as a new member or not. My point is that 1) a member template explicit specialisation is declared outside the class definition 2) its body is the body of a member function (if it is a function template).
  • curiousguy
    curiousguy almost 12 years
    (...) "you can of course also instantiate member templates outside the class." Yes, but you cannot rewrite their definition. "In neither case do I think that the class has been "re-opened"," With an explicit specialisation, the class can be "re-opened".
  • Steve Jessop
    Steve Jessop almost 12 years
    You could view it as 'the "potential" declaration of an infinite set of members', but as I say above I believe it is incorrect to do so. I believe there is one member, which is the template. Adding specializations to a function template never affects whether or not the template is selected by the lookup rules, it only affects how the instantiation is generated. Which is why in general you should overload function templates rather than specializing them, although in the case of class members you have no choice once the class is defined, since it cannot be re-opened to add overloads.
  • Steve Jessop
    Steve Jessop almost 12 years
    Anyway, we're arguing about the proper the definition of the phrase "re-open a class to add new members", which is irrelevant to the question. With the meaning of the phrase that I have defined in these comments (specializing a member template is not "re-opening to add new members") the statement I made in my answer is true. With your definition of the phrase, the statement is false but could be made true by adding extra weasel-wording. Both ways the answer to the question is, "because if you leave the private members out of the class definition there's no mechanism add them later".
  • curiousguy
    curiousguy almost 12 years
    I am not sure a philosophical discussion about "what a member is" is needed here. A template member cannot be a virtual function. So this is not a general tool for adding "later" arbitrary members to a class definition. A template function member is much like a friend function template, it has access to members. The "re-opening" is limited to writing code with access to private members.
  • balki
    balki over 11 years
    With c++11, overloads can be explicitly deleted with = delete syntax.