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 ofDerived1
, with a slightly differentf()
, 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 initializeobject_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;
}