Can you disallow inherited private members being called through the parent at compile time?

If you have a functionality rich class, perhaps you don't have your own / manager, it often happens that you want to add some functionality, so getting it makes sense.

Sometimes you also want to subtract, that is, disallow some part of the base interface. A common idiom I've seen is to infer and make some member functions private and then not execute them. As shown below:

class Base
{
public:
  virtual void foo() {}
  void goo() { this->foo(); }
};

class Derived : public Base
{
private:
   void foo();
};

      

somewhere else:

Base * b= new Derived;

      

and one more place:

b->foo();  // Any way to prevent this at compile time?
b->goo();  // or this?

      

It looks like if the compilation doesn't know it was received, the best you can do is not implement and fail at runtime.

The problem arises when you have a library that you cannot modify, that accepts a pointer to a base, and you can implement some of the methods, but not all. Thus, part of the library is useful, but you run the risk of dropping the main dumping if you don't know at compile time which functions will call which.

To make it more difficult, others might inherit a class from you and want to use the library, and they might add some of the functionality that you didn't.

Is there another way? in C ++ 11? in C ++ 14?

+3


source to share


4 answers


Let's analyze this by focusing on two main points:

class Base
{
public:
    virtual void foo() {} // This 1)
// ... 
class Derived : public Base // and this 2)

      

In 1) you tell the world that each object Base

offers a method in foo()

public. This means that when I Base*b

, I can call b->foo()

- and b->goo()

.

B 2) you tell the world that your class Derived

is behaving like Base

. Thus, the following is possible:

void call(Base *b) { b->foo(); }
int main() {
    Derived *b = new Derived();
    call(b);
    delete b;
}

      

Hopefully there is no way to call(Base*)

know if it is b

derivative and hence it cannot decide at compile time unless the call foo

is legal.




There are two ways to deal with this:

  • You can change the visibility foo()

    . This is probably not what you want, because other classes can be derived from Base

    , and someone wants to call foo

    afterall. Be aware that virtual methods can be private , so you should probably declare Base

    as
class Base
{
  virtual void foo() {}
public:
  void goo() { this->foo(); }
};

      

  • You can change Derived

    so that it inherits from protected

    either . This means that no one / only inheriting classes can "see" what is , and calling / is not allowed:private

    Base

    Derived

    Base

    foo()

    goo()

class Derived : private Base
{
private:
    void foo() override;
    // Friends of this class can see the Base aspect
// .... OR
// public: // this way
    // void foo(); // would allow access to foo()
};
// Derived d; d.goo() // <-- illegal
// d.foo() // <-- illegal because `private Base` is invisible

      

Usually you should skip to the latter, because it does not involve changing the interface of the class Base

β€” the β€œreal” utility.




+1


source


TL; DR: Getting a class is a contract providing at least this interface. Subtraction is not possible.

This is similar to what you want to do:

struct Library {
    int balance();
    virtual int giveth(); // overrideable
    int taketh(); // part of the library
};

/* compiled into the library object code: */
int Library::balance() { return giveth() - taketh(); }

/* Back in header files */
// PSEUDO CODE
struct IHaveABadFeelingAboutThis : public Library {
    int giveth() override; // my implementation of this
    int taketh() = delete; // NO TAKE!
};

      

Thus, you cannot call taketh()

on IHaveABadFeelingAboutThis

even if it is selected as the base class.



int main() {
    IHaveABadFeelingAboutThis x;
    Library* lib = &x;
    lib->taketh(); // Compile error: NO TAKE CANDLE!
    // but how should this be handled?
    lib->balance();
}

      

If you want to expose a different interface than the underlying library, you need a facade to represent your interface, not the library.

class Facade {
    struct LibraryImpl : public Library {
        int giveth() override;
    };
    LibraryImpl m_impl;

public:
    int balance() { return m_impl.balance(); }
    virtual int giveth() { return m_impl.giveth(); }
    // don't declare taketh
};

int main() {
    Facade f;
    int g = f.giveth();
    int t = f.taketh(); // compile error: undefined
}

      

+1


source


While I don't think your overall situation is good design, and I share many of the sentiments in the comments, I can also appreciate that there is a lot of code involved that you have no control over. I don't believe there is any compile-time solution to your problem that has well-defined behavior, but what is much preferable to making the methods private and not executing them is to implement the whole interface and just make any methods you don't handle it. throw an exception. So at least the behavior is defined and you can even try / catch if you think you can recover from a library function requiring an interface that you cannot provide. I think I'm doing better than a bad situation.

+1


source


If you have class A:public B

one you should follow https://en.wikipedia.org/wiki/Liskov_substitution_principle

The Liskov substitution principle is that pointer-to-A can be used as pointer-B under any circumstance. Any requirements it B

has A

must be met.

This is difficult to do, and it is one of the reasons many people find OO type inheritance much less useful than it seems.

Yours base

provides virtual void foo()

. A regular contract means that one foo

can be called, and if its preconditions are met, it will return.

If you stem from base

, you cannot reinforce the preconditions and not relax the postconditions.

On the other hand, if it base::foo()

was documented (and consumers are supported base

) to throw it away (say method_does_not_exist

), then you could get, and your implementation will throw this error.Note that even though the contract says it can do it, in practice if this is not checked, consumers may not work.

Violating Liskov's Substitution Principle is a great way to end up with lots of bugs and unreachable code. Only do this if you really need to.

+1


source







All Articles