'How does virtual dispatch interact with covariant return types?
With C++ support for covariant return types, you can write the following:
class Base {};
class Derived : public Base {};
...
class A {
public:
A(Base* b) : b_(b) {}
virtual Base* Method() { return b_; }
private:
Base* b_;
}
class B : public A {
public:
B(Derived* b) : A(b) {}
// Return `Derived*` type so callers can access functionality specific to
// `Derived` (i.e., not in `Base`).
Derived* Method() override {
static_assert(std::is_base_of_v<Base, Derived>);
// Need to call `A::Method()` since `b_` is private.
// Alternatively, we can make `b_` protected and access it directly.
return dynamic_cast<Derived*>(A::Method());
}
}
It seems like in most cases that use covariant return types, the derived class's method (B::Method() above) will just do a dynamic cast and return the narrower pointer type. It will not do anything else.
Given that assumption, is it generally better to not use virtual dispatch (i.e., remove the virtual and overridden keywords) for Method() to improve performance? After all, any generic code that operates on an A* type will have to use the Base* type to store the return value of A::Method() (even if the real type of the object that the pointer points to is Derived), but as long Base uses virtual dispatch properly, then the correct methods will be called on Base* whether they are Base's methods or Derived's methods.
Solution 1:[1]
It seems like in most cases that use covariant return types, the derived class's method (B::Method() above) will just do a dynamic cast and return the narrower pointer type. It will not do anything else.
I would not be so sure about that.
These two features serve two different purposes:
virtualmethods allow virtual dispatch - runtime choice of the code to execute based on the runtime type of the caller.- return type covariance leverages the type system to further constraint the return type as long as the "minimal API" given by
Baseis satisfied. This is compile-time check.
I would strongly argue that B::Method should not exist in it's current form at all, let's take this hierarchy:
struct Base{
virtual ~Base()=default;
};
struct D1:Base{};
struct D2:Base{};
struct A {
virtual Base* Method() { return new D1{}; }
};
struct B1 : A {
D1* Method() override {
return dynamic_cast<D1*>(A::Method());
}
};
struct B2 : A {
D2* Method() override {
return dynamic_cast<D2*>(A::Method());
}
};
and this usage:
#include <cassert>
int main()
{
B1 b1;
B2 b2;
A* a = &b1;
assert(a->Method()!=nullptr);
a = &b2;
assert(a->Method()==nullptr);
}
That seems a little strange, how would you write the documentation of this A::Method? What could it be used for? A filter maybe?
I think the more common example, at least from what I've seen, is that the derived method produces a derived result on its own. Take for example hiearchy of Request, Response with virtual Response* Request::process method. Special requests likely return special responses.
To answer your question:
If you remove virtual, you lose virtual dispatch, if the user code can do with static polymorphism, sure. Just give it some though beforehand, in my experience, if you start replacing member variables of type Base* with T in some Foo, the templated code needed to handle storage of these now heterogenous Foo<T> tends to spread rather quickly. And for what, performance only? Surely you are considering this only because you measured the code and it is a clear bottleneck in your application.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
