Thursday, March 31, 2011

Preventing Virtual Method Implementation in C++

I have the following class hierarchy in C++:

class Base {
    virtual void apply() = 0;
};

class Derived : public Base {
    virtual void apply() {
        // implementation here that uses derived_specialty
    }

    virtual void derived_specialty() = 0;
};


class Implementation : public Derived {   
    virtual void derived_specialty() {
        // implementation
    }
};

I'd like to guarantee that classes at the level of Implementation don't supply their own implementation of apply, and that they only implement derived_specialty. Is there any way to guarantee that classes inheriting from Derived will not implement apply, so that the Derived::apply implementation is used? My understanding is that in C++, a method made virtual in the Base class is virtual all the way down the inheritance hierarchy, but if there are any tricks in C++ to accomplish, I'd be interested in hearing about them.

I'm always surprised by the things that are allowed by C++, so I thought it was worth asking. :)

From stackoverflow
  • You can kind of do it by composition:

    class Base {
        virtual void apply();
    };
    
    class Derived : public Base {
    
        class IImplementation {
            virtual void derived_specialty() = 0;
        };
    
        IImplementation& m_implementation;
    
        Derived(IImplementation& implementation)
            : m_implementation(implementation)
        {}
    
        virtual void apply() {
            // implementation here that uses m_implementation.derived_specialty
        }
    
    };
    
    
    class Implementation : Derived::IImplementation {   
        virtual void derived_specialty() {
            // implementation
        }
    };
    

    Other classes can still subclass Derived and override the apply method, but your Implementation class is no longer one of these classes.

    ChrisW : Would you call this 'composition'? Maybe 'delegation' is a better name for it.
    strager : Very nice idea! But what about the Base derivation bits?
    ChrisW : @ strager I don't understand your question: what *bits*, and yes, *what* about them?
    strager : Implementation now does not derive from Base, which is a problem. Derived doesn't, either.
    ChrisW : My mistake: I've edited to show Derived deriving from Base. Thanks for pointing thatit's out. However I don't see a problem with Implementation not deriving from Base.
    strager : Ah, the lack of derivation confused me even more than it needed to. You're right; IImplementation doesn't need it.
  • Make the restriction clear in your documentation.

  • You could make Implementation a delegate class rather than a specialization of Derived

    class Derived : public Base
    {
        Derived()
    
        void apply() 
        {
            //whatever, delegate to impl class instance
            impl->apply_specialization();
        }
    
    
        Impl* impl;
    };
    
    class Impl : public WhateverImplInterface
    {
          void apply_specialization(){}
    };
    

    Then the implementation doesn't have access to the apply function and is separated from the hierarchy. The Derived class is then parameterized by an instance of Impl class.

    Dave Hillier : I prefer to give classes more meaningful names than Impl (I assume its just abstract for the example). It makes searching the code easier and is also less likely to confuse my IDE.
    : @Dave, yes it's only for generality in the context of an example
    Ismael : Nitpick corner coming soon to SO.
  • You could make Base::apply non-virtual and use the template method pattern within Base too.

    This article explains advantages of this practice:
    http://www.gotw.ca/publications/mill18.htm

  • "I'd like to guarantee that classes at the level of Implementation don't supply their own implementation of apply."

    You can't.

    None of the examples I've seen so far prevent any of the derived classes from defining their own apply function. They all provide methods for modelling the relationship between apply and derived_specialty, suggesting to users that they shouldn't override apply. You can achieve the same in a line of documentation, though.

    What you're looking for is the Java final statement that doesn't exist in C++, right?

  • You can put an assertion in the destructor to make sure apply was not overridden:

    class Base {
        virtual void apply() = 0;
    };
    
    class Derived : public Base {
        virtual void apply() {
            // implementation here that uses derived_specialty
        }
        virtual ~Derived() {
            assert(this->apply == Derived::apply);
        }
        virtual void derived_specialty() = 0;
    };
    
    
    class Implementation : public Derived {   
        virtual void derived_specialty() {
            // implementation
        }
    };
    

    The idea here is that this->apply will get the method address from the virtual table, while Derived::apply resolves it in compile time. If they are equal, apply was not overridden again in the Implementation class. This approach has also the advantage that it imposes no performance penalty in the release build, where assert() macros are (should be) stripped from the generated code.

    CAdaker : Maybe I'm just not grasping something, but if apply() is overriden, then the assertion code would never be called, no? If the assertion is ever checked, then we are by definition in the right apply() method...
    Fabio Ceconello : You're right, it would be better to put the assert() in the destructor, so you'd be sure it is invoked sometime.
    Fabio Ceconello : Of course this will give the programmer a warning only after the fact. If you have another method which you know will be invoked before apply(), you could place the assert() in it.
    CAdaker : Actually, I just tried it and it doesn't even compile... the this->apply construction isn't valid (and neither is &this->apply or any other construction i could think of). Maybe it simply isn't possible to do this in standard C++...
    Fabio Ceconello : In which compiler? Before posting the example, I compiled it in VS2005 without complains.
    CAdaker : Hmm... gcc-4.3.3 complains about "invalid use of member (did you forget the '&' ?)". With an '&' you instead get "ISO C++ forbids taking the address of a bound member function to form a pointer to member function."
    Fabio Ceconello : You're right, it seems g++ is more strict than the others, even when using it just for comparison, the compiler blocks the access to the bound virtual method.
  • Try using template method pattern

    Wikipedia has a C++ example.

    It doesn't change the encapsulation, but it improves the design so you don't need to.

  • There's always access modifiers:

     class base {
          protected: virtual void real_apply() = 0;
     };
     class derived : public base {
          void real_apply();
     public:
          apply() { real_apply(); }
     };
     class other : public derived {
          void func() {
              apply();      // this is ok
              real_apply(); // this is a compile time error
          }
     };
    

0 comments:

Post a Comment