Search code examples
c++inheritancetype-conversioncopy-constructorc++98

Creating a "view" of a sibling class avoiding unnecessary copy


Consider this (simplified) class hierarchy in a c++98 source (godbolt):

class Shape
{
 public:
    virtual class CurvilinearShape as_curvilinear_shape() const =0;
};

class CurvilinearShape : public Shape
{
    // ...
 public:
    virtual CurvilinearShape as_curvilinear_shape() const
       {
        return *this;
       }
};

class PolygonShape : public Shape
{
    // ...
 public:
    virtual CurvilinearShape as_curvilinear_shape() const
       {
        CurvilinearShape curv_shape;
        // Convert...
        return curv_shape;
       }
};

A PolygonShape can be easily converted to a CurvilinearShape. The reason that led to those two distinct classes is that a PolygonShape has a simplified description that can be processed by simpler machines, while more advanced ones can process the more generic CurvilinearShape descriptor:

void work_curvilinear_shape(CurvilinearShape const&);

In this case it's very handy to process a sequence of base Shape items without worrying about the actual descriptor used, the virtual member function as_curvilinear_shape() was introduced just for that:

#include <vector>

int main()
{
    std::vector<Shape*> shapes;
    for(std::vector<Shape*>::const_iterator it=shapes.begin(); it!=shapes.end(); ++it)
       {
        work_curvilinear_shape( (*it)->as_curvilinear_shape() );
       }
}

This works, but has the problem of the unnecessary copy construction that takes place each time CurvilinearShape::as_curvilinear_shape() is called (incidentally the majority of the times).

Although this seems a fairly common use case I cannot find a name for it, do you know it? I'm searching for a better approach that avoids the unnecessary copy in case the item is already a CurvilinearShape instance, maybe one of you can put me on the right track?


Solution

  • A (somewhat cumbersome) solution is to give as_curvilinear_shape the following signature:

    const CurvilinearShape &as_curvilinear_shape(CurvilinearShape &storage) const;
    

    If it's already the right shape, return *this and leave storage unchanged. If a conversion is needed, assign the conversion result to storage and return a reference to storage.

    Or you might want to create a helper class and return it:

    struct CurvilinearShapeOrRef
    {
        CurvilinearShape storage; // or `std::optional`/`boost::optional`.
        const CurvilinearShape *ptr_override = nullptr; // C++98 can't do this, make a constructor.
    
        const CurvilinearShape &get() const {return ptr_override ? *ptr_override : storage;}
    };