marital_weeping
marital_weeping

Reputation: 866

Calling a class with an abstract base class from a derived class --safely

I'd like to call the class Foo which has the abstract class Base in its ctor. I'd like to be able to call Foo from Derived which is derived from Base and use Derived's overriding methods rather than Base's.

I'm only able to do this by using a raw pointer as indicated. Is there any way to do this without raw pointers? I tried std::shared_ptr but the compiler complains about abstract classes. Or perhaps is there a better way?

#include <iostream>

class Base {
public:
    Base() {
        std::cout << "Hello from Base." << std::endl;
    }

    virtual void show() const = 0;
};

class Foo {
public:
    explicit Foo(const Base  *s)  { // can I avoid this raw pointer?
        std::cout << "Hello from Foo." << std::endl;
        s->show();
    }
    Base *s;
};

class Derived : public Base {
public:
    Derived() : Base() {
        std::cout << "Hello from Derived." << std::endl;
        Foo(this);
    }

    void show() const override {
        std::cout << "Hi, I'm Derived::show()." << std::endl;
    }
};

int main() {
    Derived();
    return EXIT_SUCCESS;
}

which produces the following output:

Hello from Base.
Hello from Derived.
Hello from Foo.
Hi, I'm Derived::show().

Upvotes: 3

Views: 113

Answers (1)

Sebastian
Sebastian

Reputation: 1974

The code can be rewritten with const reference to Base as

#include <iostream>

class Base {
public:
    Base() {
        std::cout << "Hello from Base." << std::endl;
    }

    virtual void show() const = 0;
};

class Foo {
public:
    explicit Foo(const Base& b) : s(b) { // member initialization list to set s
        std::cout << "Hello from Foo." << std::endl;
        s.show();
    }
    const Base& s;
};

class Derived : public Base {
public:
    Derived() : Base() {
        std::cout << "Hello from Derived." << std::endl;
        Foo(*this); // the parameter would be the object itself *this, instead of a pointer this
    }

    void show() const override {
        std::cout << "Hi, I'm Derived::show()." << std::endl;
    }
};

int main() {
    Derived();
    return EXIT_SUCCESS;
}

A reference should be initialized in a member initializer list of the constructor.

When using a raw pointer or a reference 'Foo' should not own 'Base', i.e. Foo is not responsible for destroying Base and the lifetime of Base should be guaranteed by the owner of Foo. You have to make sure that Base (=Derived in this case) outlives Foo. That is guaranteed, if the Foo object is owned by Derived, e.g. as member or local variable. Then before Base=Derived is destroyed, Foo is destroyed.

You can use normal references instead of const references, but then the same for the constructor parameter as well as the member variable.

A raw pointer (in comparison to a reference) is idiomatic in cases,

  1. where the parameter can also be the nullptr instead of a valid object or
  2. where the pointer is exchanged with another one during the lifetime of the object.

The first case could be handled by std::optional instead, the second one with assignment of a lightweight object ('view'), which basically encapsulates a pointer or a reference.

So very few cases (e.g. low-level code, data-structures or for compatibility with C) are left, where raw pointers would still be used in modern C++. And even in those cases, having a wrapper object, which just stores a reference as member variable, would have the same performance (and in practice also the same memory layout) as raw pointers, but are much more clean and safe to use.

In some cases, you would prefer a raw pointer to std::optional for performance reasons, when execution speed or memory size really matters. As alternative, a reference to nullptr is not allowed in C++.

Upvotes: 3

Related Questions