Home > other >  How to avoid a virtual function for a single derived class implementation?
How to avoid a virtual function for a single derived class implementation?

Time:01-01

I have a an Accessor class defining my interface to other classes and multiple base class objects within this Accessor class implementing stuff in various flavors.

class Accessor
{
    std::shared_ptr<Base> object1;
    std::shared_ptr<Base> object2;
}

The Accessor class implements of course more than just calls to the different objects, but there is one particular function, which indeed only redirects the function call to one of the objects.

The problem is now that this particular function is supposed to be only implemented in one derived class and when calling the function using the Accessor class, it is known that the particular object is always the derived class implementing this method. Currently, I'm defining an 'empty' base class method and override it only in the mentioned derived class.

However, I don't like this approach for two reasons: first, it looks ugly from the design perspective and second, I still have a virtual function call (and the discussed function here is in the hot path of a loop).

In summary a minimal version of the code looks as follows:


class Base 
{ 
    virtual void f() const { std::cout << "Dummy implementation!\n";}
};
 
class Derived1 : Base
{
    void f() const override { std::cout << "Implementing the actual thing!\n";}
};

class Derived2 : Base
{
   // Carrying the dummy implementation of the base class
};

class Accessor
{
    std::shared_ptr<Base> object1;
    std::shared_ptr<Base> object2;

    // When calling this function, we know that we actually 
    // call object1 of type `Derived1`. Still, there might be 
    // cases where object1 has a different type, but then we don't 
    // call this function 
    void f() const { object1->f();}
}

The whole description is probably somewhat a sign that the overall design is not the best choice. However, maybe there are other options to redesign the problem here so that the virtual function call vanishes and the code reflects the actual situation better.

CodePudding user response:

If you are sure that object_1 always points to a Derived1, then make it a std::shared_ptr<Derived1> object1. And if the sole purpose of Base::f() is for Derived1, make it non-virtual and remove it from the classes that don't need it.

But if one of those assumptions is not true, keep it as it is:

  • Making an extra test to check if the class is Derived1 before calling a non-virtual function is not more efficient than calling directly a virtual function. If you don't trust me, make a benchmark. Moreover, if in the future you'd have a further specialization of Derived1, with a slightly different f(), you'd not have to worry.

  • Assuming that it's always Derived1 and just statically down-casting is risky. As said, if you're so sure, just declare it as such (go to first paragraph), and make a (safe) downcasting when you initialize object_1.

Remark: While it may seem weird at beginning to have a f() that is useless in most cases, it is in fact a common practice when using polymorphism with the tell, don't ask paradigm. A very typical example is when using the template method pattern.

CodePudding user response:

You can avoid the base class and virtual method with std::variant, it is a bit slower than virtual function calls, though. You can test if the Derived1 type is in the variant.

    class Derived1
    {
    public:
        void f(size_t i) {   _i; }
    private:
        size_t _i {0};
    };

    class Derived2
    {
    public:
    };

    class Accessor
    {
    public:
        template<typename S>
        void add_one (std::shared_ptr<S> val) {
            object1 = val;
        }
        // When calling this function, we know that we actually
        // call object1 of type `Derived1`. Still, there might be
        // cases where object1 has a different type, but then we don't
        // call this function
        void f(size_t i) const {
            // you can check the type with this
            //if (std::holds_alternative<std::shared_ptr<Derived1>>(object1)) {
                std::get<std::shared_ptr<Derived1>>(object1)->f (i);
            //}
        }
    public:
        std::variant<std::shared_ptr<Derived1>, std::shared_ptr<Derived2>> object1;
    };

    int main ()
    {
        Accessor obj;
        obj.add_one (std::make_shared<Derived2>());
        return 0;
    }

  • Related