When a function needs to return an object. Should it return it through a pointer to derived or base?
class B{
}
class D:public B{
}
// way 1: return pointer to derived
D* createDerived(){
D* d = new D();
return d;
}
// way 2: return pointer to base
B* createDerived(){
B* d = new D();
return d;
}
I have heard of "program to an interface not an implementation" which would suggest that we should return a pointer to base. However my intuition says it is better in this case to return a pointer to derived, because if the client code uses base pointers, this function would still work! On the other hand, if we return pointer to base and the client code uses derived pointers, this would not work for them. It seems that by returning a more "specific" pointer, we are allowing more flexibility for client code.
Another way to look at it is from the perspective of "program by contract." One of the suggestions is to promise as little as you can. By promising that we will return a very specific object, we follow this rule. However if we return a base pointer, it seems to me that we are promising a lot more.
Which is better design? Is my reasoning above correct?
I have a lot to learn on how to make modular, maintainable, extensible software, so please excuse if my reasoning/conclusion is nooby. I am very interested in learning. Thank you so much for your time.
It isn't possible to answer this question in a general way. In particular, returning the more derived object imposes additional restrictions on future implementations of the method, while returning the base class imposes more restrictions on the caller. Which is best depends on the design of the application or library, and in particular the scope of functionality offered by B
and D
and the overall design of the API.
In general, you want to return the most-derived, or, loosely speaking, the most functional class, which doesn't constrain your future implementation choices. This allows your clients to use the return value efficiently, while still allowing you to the change the implementation in the future.
The primary downside of using the derived class D
is that you expose more details to the client, which may be difficult or impossible to reverse later.
For example, imagine that that you have a method reverse(std::ReversibleContainer &cont)
, which takes a container and returns a reversed snapshot of it (i.e., changes to the underlying container don't effect the returned snapshot).
In your initial implementation, you might decide to implement this as:
template<class BidirectionalIterator>
std::list<T> reverse(BidirectionalIterator &start, BidirectionalIterator &end) {
std::vector output;
std::copy(input.begin(), input.end(), back_inserter(output))
return output;
}
Later on, you might realize that you can avoid the copy of the underlying data for certain cases where the container (and elements) are constant, for example:
ImmutableIterator reverse(ImmutableBiderectionalIterator &input) {
return ReversingImmutableBiderectionalIterator(input);
}
This container can use the knowledge that the input container is read-only to return a view of the input container, avoiding the copy, which simply remaps each access to result in the same semantics as a reversed container.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With