Should functions return a pointer to a derived or base class?

When a function should return an object. Should he return it through a pointer to a derivative 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 a "program for an interface, not an implementation," which suggests that we should return a pointer to the base. However, my intuition says that in this case it is better to return a pointer to a derivative, because if the client code uses basic pointers, this function will still work! On the other hand, if we return a pointer to the base, and the client code uses derived pointers, this will not work for them. It seems that by returning a more "specific" pointer, we provide more flexibility for client code.

Another way to look at this is from the perspective of a β€œcontracted program.” One suggestion is to promise as little as possible. By promising that we will return a very specific object, we follow this rule. However, if we return the base pointer, it seems to me that we promise a lot more.

Which design is better? Are my arguments above correct?

I have a lot to learn how to make modular, supported, extensible software, so please excuse me if my reasoning / conclusions are nooby. I am very interested to learn. Thank you so much for your time.

+5
source share
3 answers

It is impossible to answer this question in a general way. In particular, the return of a more derived object imposes additional restrictions on future implementations of the method , and the return of the base class imposes more restrictions on the caller . This best depends on the design of the application or library and, in particular, on the amount of functionality offered by B and D and the general design of the API.

In general, you want to return the most derivative or, to put it mildly, the most functional class that does not limit your future implementations. This allows your customers to make good use of the return value, while maintaining the ability to change in the future.

The primary disadvantage of using a derived class D is that you provide the client with more detailed information, which may be difficult or impossible to cancel later.

For example, imagine that you have a reverse(std::ReversibleContainer &cont) method reverse(std::ReversibleContainer &cont) that takes a container and returns a snapshot (i.e. changes to the base container do not affect the returned snapshot).

In your initial implementation, you may 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; } 

You can later understand that you can avoid copying the underlying data for certain cases where the container (and elements) are persistent, for example:

 ImmutableIterator reverse(ImmutableBiderectionalIterator &input) { return ReversingImmutableBiderectionalIterator(input); } 

This container can use the knowledge that the input container is read-only to return the representation of the input container, avoiding copying, which simply reassigns each access to the result in the same semantics as the inverse container.

+2
source

I suggest that you rename the member functions for createB and createD, and in the second case, return the pointer to the derived class, because you can always overlay the pointer of the derived class to the base class when the inverse transformation may fail.

0
source

Imo's correct answer is neither one nor the other. Usually there is no need to return the original pointers from functions in your own code (exceptions below).

Instead, simply return the created object:

 class B { virtual ~B(){} }; class D : public B {}; // this function would make real sense only if D were a class template auto createD() { return D{}; } 

This is enough anyway. Moreover, and, importantly, it expresses what you get, namely an object of type D , which you can use as needed. Instead, when you get a raw pointer, it is from the first (i.e., not realizing the behavior on behalf of a function, reading documentation, etc.), It is not clear what you are allowed to do with this raw pointer. Can you wrap it inside unique_ptr ? Can you delete it? Unclear. Returning an object (or, better, a well-designed object, which basically means serial RAII) frees you from having to answer this question - just do what you want with the object.

Also, if you really need a pointer (which is an abstraction from an object), you can still put the return inside a suitable smart pointer, for example

 auto d_uptr = std::make_unique<D>(createDerived()); auto d_sptr = std::make_shared<D>(createDerived()); 

or, likewise, also in smart base class pointers,

 std::unique_ptr<B> = std::make_unique<D>(createDerived()); std::shared_ptr<B> b_sptr = std::make_shared<D>(createDerived()); 

In this case, a copy of elision is used to construct the pointers and does not create overhead compared to your function returning D* . Please note that, as a rule, a pointer should be a smart pointer, because only then will you be released directly from the obligation to correctly delete it somewhere later in the code.

The only exception where you need source pointers as return function types is the cloning pattern, which is used when you want to copy an object using a base class pointer. Here you need to use smart pointers, as well as a function called by the user, but you need to use raw pointers inside the class to allow covariant types of returned virtual functions:

 class B { virtual ~B(){} auto clone() const { return std::unique_ptr<B>(clone_impl()); } protected: virtual B* clone_impl() const = 0; }; class D : public B { protected: virtual D* clone_impl() const { return new D{*this}; }; }; 

There can be many other exceptions (for example, always when covariance is used) that I don’t remember at the moment. But they are not so important.

To summarize: don't use source pointers as return functions unless you have a good reason for this.

0
source

Source: https://habr.com/ru/post/1243437/


All Articles