1

I came from Java where it's not allowed to decrease access modifiers in derived classes. For instnace, the following is not compile in Java:

public class A{
    public void foo(){ }
}

public class B extends A{
    @Override
    private void foo(){ }  //compile-error
}

But, in C++ it's fine:

struct A {
    A(){ }
    virtual ~A(){ }
    A(A&&){ }
public:
    virtual void bar(){ std::cout << "A" << std::endl; }
};

struct B : public A{
private:
    virtual void bar(){ std::cout << "B" << std::endl; }
};

int main()
{
    A *a = new B;
    a -> bar(); //prints B
}

DEMO

Where might it be useful? Moreover, is it safe to do so?

stella
  • 2,416
  • 1
  • 14
  • 29
  • 1
    As an aside, the `public` modifier in struct A does nothing because everything in a struct is public by default. – MrEricSir Dec 22 '15 at 04:11
  • 2
    Personally, I wouldn't recommend changing the access level as I think it could be confusing. However, I don't know any reason why it would be considered unsafe nor do I know why it would be useful. Related, it can be useful to have a private virtual function called by a non-virtual public function. See the NVI (non-virtual interface) idiom. – James Adkison Dec 22 '15 at 04:19
  • @JamesAdkison It's "unsafe" because it lets outside code call a private function directly, which contradicts the entire point of private functions. – user253751 Dec 22 '15 at 04:23
  • @immibis I mean unsafe from an UB point of view. And the base class intended it to be public so IMO it's wrong for the derived class to expect anything less than public access. – James Adkison Dec 22 '15 at 04:25
  • @JamesAdkison Exactly. So why is the derived class *allowed* to make it less than public? – user253751 Dec 22 '15 at 04:25
  • @immibis Simply because the language doesn't prevent it. – James Adkison Dec 22 '15 at 04:27
  • 2
    Another answer: http://stackoverflow.com/questions/484592/overriding-public-virtual-functions-with-private-functions-in-c – kkm Dec 22 '15 at 05:01
  • @JamesAdkison Restating the question I see. Why doesn't the language prevent it? – user253751 Dec 22 '15 at 06:32
  • @immibis "As Bjarne put it, the access control in C++ is meant to protect against Murphy, not Machiavelli. The same is true in general -- it's features are meant to protect against accidents, not people intentionally doing something wrong." - [How to prevent derived class from making a private/protected virtual function public?](http://stackoverflow.com/questions/2143933/how-to-prevent-derived-class-from-making-a-private-protected-virtual-function-pu) – James Adkison Dec 22 '15 at 06:54
  • @JamesAdkison I honestly feel like I've given a good answer to this question that the duplicates listed have failed to do. In this situation, am I supposed to copy and paste my answer into those previous questions? Seems odd, and plus it will not be effective since those questions already have accepted answers with plenty of upvotes; my answer will be at the bottom. This seems like a situation where a technically superior answer is guaranteed to be buried because it comes at a later time, and SO does not have a good mechanism for dealing with it. – Nir Friedman Dec 22 '15 at 17:46
  • @NirFriedman I understand your frustration and I've been in the same situation before. I added my answer to the existing question and have received votes for it. You can search the meta site for more help, information, and tips (e.g., [Is there a way to promote a brand new answer to an old question crowded with many outdated answers?](http://meta.stackexchange.com/questions/238717/is-there-a-way-to-promote-a-brand-new-answer-to-an-old-question-crowded-with-man)). – James Adkison Dec 22 '15 at 21:50

1 Answers1

3

As you observed, the access specifier works based on the type of the pointer, not based on the dynamic type. So specifying private in B in this case simply means the functions cannot be accessed through a pointer to B. This could therefore be useful in keeping things locked down in cases where the client should not being using pointers to B (or creating B's on the stack). Basically, in cases where B's constructor is private and you create B's (and possibly other children of A) through a factory that returns a unique_ptr<A>. In this case, you could just specify all of B's methods as private. In principle this prevents the client from "abusing" the interface by dynamic casting the unique_ptr downwards and then accessing B's interface directly.

I don't really think this should be done though; it's a more Java-y approach than C++. In general if the client wants to use a derived object on the stack instead of on the heap through a base class pointer, they should be able. It gives better performance and it's easier to reason about. It also works better in generic code.

Edit: I think I should clarify. Consider the following code:

enum class Impl {FIRST, SECOND, THIRD};

unique_ptr<A> create(Impl i) {
  ...
}

Suppose this is the only way to create concrete instances that use A's interface. I could desire perhaps that the derived classes are pure implementation details. For instance, I could implement each of the three implementations in a different class, then later decide that two of the three can be lumped together into one class with different options, and so on. It's none of the user's business; their world is just A's interface plus the create function. But now suppose a user happens to look at the source and knows that the FIRST implementation is implemented using B. They want better performance, so they do this:

auto a = create(Impl::FIRST);
auto b = dynamic_cast<B *>(a.get());
// Use b below, potentially avoiding vtable

If a user has code like this, and you eliminate or rename the class B, their code will break. By making all of B's methods private, you make the b pointer useless to the user, ensuring that they use the interface as intended.

As I said before, I don't particularly advocate programming this way in C++. But there may be situations where you really do need the derived classes to be pure implementation details; in those cases changing the access specifier can help enforce it.

Nir Friedman
  • 15,634
  • 2
  • 33
  • 63