1

I find I still do an embarrassingly large number of things in c style, so I am now trying to embrace the new millennium by reducing my usage of raw pointers. I have a vector of unique_ptr<BaseClass>, that each points to an object of a derived class. I am trying to get a nice way to refer to one of the objects of the derived class. At the moment, I do this by using the .get() function and casting this to the derived class.

However, as I understand it, .get() is mostly there to interface with legacy code that insists on raw pointers, and its use should be avoided. Having another pointer to an object within a unique_ptr doesn't seem like great style if it can be avoided. Is there a way to get a reference to the derived class object, without using get? Or some other convenient way of handling the object?

Here is a simplified code example:

#include <iostream>
#include <vector>

class Fruit {
public:
    double size = 0;
    virtual ~Fruit() = default;
};

class Apple : public Fruit {
public:
    bool hasLeaf = false;
};

void doAppleStuff(std::vector<std::unique_ptr<Fruit> > &apples) {

    // assume we know that element [0] exists and it is definitely of type Apple
    auto apple = static_cast<Apple *> (apples[0].get());  // is there a better option?

    if (apple->hasLeaf) {
        std::cout << "We can leaf now" << std::endl;
    } else {
        std::cout << "We are pitiably leafless" << std::endl;
    }
}

int main() {
    std::vector<std::unique_ptr<Fruit> > fruitVec;
    auto apple = new Apple;
    apple->hasLeaf = true;
    std::unique_ptr<Fruit> applePt(apple);
    fruitVec.push_back(std::move(applePt));
    doAppleStuff(fruitVec);
    return 0;
}

(I think that it's probably possible to shorten the main function with make_unique from c++14).

Would it be good coding style to do something like this?

auto &apple = *static_cast<Apple *> (apples[0].get());

The answer at "Downcasting" unique_ptr<Base> to unique_ptr<Derived> outlines a method to "cast" the unique_ptr by releasing it and recreating a new unique_ptr to the derived class, but that doesn't seem applicable here, as I don't really want to mess with the vector of unique pointers (unless I am missing something).

Martin Cook
  • 554
  • 5
  • 15
  • 1
    As `Fruit` has no virtual destructor, your code is currently UB. – Jarod42 Dec 06 '17 at 09:51
  • If you insist on storing classes using pointer to base class then you need to declare some virtual methods (especially destructor) and call them instead of upcasting. – user7860670 Dec 06 '17 at 09:51
  • @Jarod42 Excellent point... I will edit the code to add a virtual destructor – Martin Cook Dec 06 '17 at 10:00
  • And once you use polymorphism, virtual method and visitor would allow to avoid manual casting. – Jarod42 Dec 06 '17 at 10:03
  • I would prefer not to create virtual methods in the base class, because I don't want the base class to know about methods that are specific to the derived class. Most of the rest of the code deals with base class objects in a polymorphic manner, but this section of code is really specific to only one derived class and methods that only this particular derived class has. – Martin Cook Dec 06 '17 at 10:10
  • A large part of the complication in your code stems from `hasLeaf` being a member of the derived type held as the base type. As such you always must cast it (typically you would prefer a `dynamic_cast` to at least fall apart in a more controlled way in case your assumption fails). You should revisit your class design, and more generally your code. I mean doing apple stuff with a bunch of fruit does not seem very intuitive. – midor Dec 06 '17 at 10:13
  • 2
    To avoid to add too many virtual method in base class, you might use [Visitor_pattern](https://en.wikipedia.org/wiki/Visitor_pattern). – Jarod42 Dec 06 '17 at 10:20
  • @midor I get what you are saying, and it would certainly be easier if it were a vector of `Fruit`. However, the code I am using does lots of other stuff polymorphically with `Fruit` objects, with different derived classes. But there is one section that only runs if we are dealing with `Apple`s, with methods that only make sense for an `Apple`. It's quite possible that my design is not optimal, and the logic that is `Apple` specific could be folded into the `Apple` class, but it's not immediately clear to me. For now, I am stuck with casting to an `Apple`. – Martin Cook Dec 06 '17 at 10:31
  • @Jarod42 Very interesting... thank you. I'll have to do some thinking to see if that is a good fit. – Martin Cook Dec 06 '17 at 10:35
  • 1
    I have been there; as @Jarod42 says you can check if virtual methods and/or visitor help. CRTP is typically also something to look into. Also read up here https://stackoverflow.com/questions/11002641/dynamic-casting-for-unique-ptr#11002936 on why dynamic casting unique pointers may be complicated. – midor Dec 06 '17 at 10:44
  • @midor Goodness gracious... I am once again reminded of the gulf between someone who knows some c++ and an expert. Thanks for the links... – Martin Cook Dec 06 '17 at 10:57

2 Answers2

2

If you know that the pointer is non-nullptr, a simpler cast is:

auto& apple = static_cast<Apple&>(*apples[0]);

This syntax works with all smart-pointers that support T& operator*() const (e.g. std::unique_ptr, std::shared_ptr, boost::intrusive_ptr).

In cases with multiple inheritance casting from-base-reference-to-derived-reference is faster than casting from-base-pointer-to-derived-pointer because the latter must always check for nullptr before adding or subtracting an offset to the pointer (so that nullptr turns into nullptr after the cast), whereas references cannot be nullptr and therefore no run-time check is necessary.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

A safer alternative is to define a helper function

template <typename T> // 
std::vector<T *> of_type(const std::vector<std::unique_ptr<Fruit>> & fruits) {
    std::vector<T *> result;
    for (auto & ptr : fruits) {
        if (T * item = dynamic_cast<T *>(ptr.get())) {
            result.push_back(item);
        }            
    }
    return result;
}

and then change doAppleStuff to

void doAppleStuff(std::vector<Apple *> & apples);

called as

doAppleStuff(as_type<Apple>(fruitVec));
Caleth
  • 52,200
  • 2
  • 44
  • 75