RSS LJ

May 15, 2008

Virtual inheritance weirdness ()

by fluffy at 5:01 PM
I was trying to see if I could emulate something like Java's final by changing the scope of an inheritance. I got some weird results.

class base {
public:
        virtual int foo() = 0;
};

class middle: public base {
private:
        int foo() { return 5; }
};

class final: public middle {
public:
        int foo() { return 10; }
};
The preceding code compiles without warning or error in g++ even though the scope of middle::foo is private, and a caller can call final::foo() but can't call middle::foo(). Further, if I make base::foo private, the code still compiles fine, but if I make base::foo private and give it an implementation, it (correctly) fails to compile because middle is trying to override a private method. (But if base::foo is public and implemented and middle::foo is private, final can still override it, indicating that it's using base's access rule.)

Is this tomfoolery in g++ or in the C++ spec? Obviously, private inheritance on a pure abstract method is a completely meaningless idea (and in that respect, that should be an error), but it seems like a class's method access rules should respect the immediate parent's, not the base class's. Either that or virtual inheritance should not allow access changes at all to begin with.

The use case in this particular circumstance is that I have a base interface class (pure virtual), an intermediate partial implementation class, and then a bunch of derivatives from that intermediate class, which I would rather not be able to override some of the bits that the intermediate class implements. i.e. I want something like Java's final keyword.

I guess the only reasonable C++ solution is to add a big @note to the intermediate class's documentation saying to not override these methods.

Comments

#10874 05/15/2008 06:50 pm Expected behavior
That's the way it is supposed to work. No one ever thought about "final" in the C++ days. Plus, I'd argue that "final" is only useful to you get performance benefits and even then is problematic. It leads to idiocy like Java's declaring String as final.
#10875 05/16/2008 09:12 am
I don't think it's supposed to work that way. It seems like it should either generate a warning about a change in access, or it should use the direct parent's access. Anyway, one of the cases I listed (private pure virtual with a public derived form) seems completely broken and nonsensical.
#10876 05/16/2008 09:21 am
But changing protection levels is a perfectly cromulent thing to do. There are situations where you might want to redefine a virtual method and also not give callers access to that method. It is not nonsensical at all.

Changing access levels is a tool, not a mistake, and doesn't need a warning.
#10877 05/16/2008 09:37 am
I think you're missing that there's a few things going on here. The one that's concerning me the most is that a pure abstract private method is being treated as valid (when it shouldn't be, since the very concept makes no sense), and furthermore that a derived class is allowed to promote it to protected or public, which, again, should not be possible.

The lesser thing which is what started this post is that a class is able to change an access level from public to private, then a further derived class is able to take it back to public, which is also anathema to the point to private (vs. protected).
#10878 05/16/2008 10:12 am
The original base class made the method public. *That* drives what all child classes can do. The "middle" class can make its version of the method private, but it can't override the protection of the base class's version, which is what you appear to want it to do. The important thing to understand is that middle isn't changing the access method of anything in the base class. It is creating a new version of the method and setting the protection level of that, and that alone.

Whether or not this leads to situations that don't seem to make sense is beside the point. As we all know, C++ is designed to let people do all sorts of things that don't make sense. Don't look for Java style programmer protection.
#10879 05/16/2008 01:11 pm
Yes but that still doesn't address the 'abstract private' case, which I guess I could distill down further because I think you're getting caught up on the multi-level inheritance issue:

class A {
private:
  virtual void foo() = 0;
};
class B {
public:
  virtual void foo() {}
};


And anyway, the multi-level inheritance issue still leads to an inherent inconsistency. A private method or member is supposed to be completely private, including to derived classes. Does the C++ spec say that the access level for inheritance is based on the first appearance of the member, or based on it in the parent?
#10880 05/16/2008 01:52 pm
Abstract private is no different from all the other silly things C++ lets you do, like "x=3,2"; or "x = sprintf;"

"private" and "public" specify whether you can call it, not whether you can derive from it. Yes, it makes no sense to have something that can't be called "private", but that's what falls out of not having to individually label things as private as in Java.

I'm also not convinced that it is entirely silly: Consider:


class A {
private:
  virtual void foo() = 0;

public:
  doIt()
  {
     foo();
  }
};

class B : public A{
public:
  virtual void foo() {}
};

class C : public A{
private:
  virtual void foo() {}
};

int main()
{
   A *a = new b;

   a->doIt();
   // a->foo();  // NOT ALLOWED

        B *b = new b;
        b->foo(); // ALLOWED
}


I can see where this sort of thing might be useful. In this case, the private abstract has a very real meaning, that is, you can't call it from a base class pointer directly.
#10881 05/16/2008 02:51 pm
Okay, my understanding was that private was entirely hidden from derived classes (and that derived-but-hidden stuff should be done with 'protected' access), but I guess that only applies to members rather than methods. My bad.