-1

Let's say I have an abstract base class that has two derived classes. Each of those derived classes has some new functionality not present in the abstract base class, but both derived classes have the same function. For example:

class MyBase:
    public:
        /* ... */
        virtual void DoSomething() = 0;
        /* ... */


class MyAlpha : public MyBase
    public:
        /* ... */
        void DoSomething() { /* does sometihng */ }

        /* Function not present in abstract base class */
        void DoSomethingNew() { /* does something new */ }
        /* ... */

class MyBeta : public MyBase
    public:
        /* ... */
        void DoSomething() { /* does sometihng */ }

        /* Function not present in abstract base class */
        void DoSomethingNew() { /* does something new */ }
        /* ... */

Now I have a templated function somewhere that accepts a pointer (in my case a std::unique_ptr) to the base class, and I want to be able to call the DoSomethingNew() function (the function that is present in both derived classes but not the base class. For example:

template <typename Base_t> void MyOperation(std::unique_ptr<Base_t> &base_object) { 
    /* some ops */
    base_object->DoSomethingNew();
}

How do I go about doing this? I feel like template specialization might be the way to go here but I'm not quite sure. I am working on extending an open source library with a new feature, so I have limitation on what existing code I can/should modify to make my feature work. The base class in my actual use case is code that I'd like to avoid modifying, but for general use in this library, my function signature needs to accept a pointer to the base class.

As the base class is virtual, the actual usage is something like:

std::unique_ptr<MyBase> object = std::unique_ptr<MyAlpha>(new MyAlpha);
MyOperation(object);

How do I go about this using derived class functionality in the MyOperation() function? If it makes a difference, I have to stay C++11 compatible.

marcman
  • 3,233
  • 4
  • 36
  • 71
  • 2
    A dynamic_cast<> will do the trick but too many of those is usually indicative of a bad design. Since both derived classes have the `DoSomethingNew()` member function, the best thing to do would be to add that to your base class – crdrisko Aug 23 '20 at 16:09
  • @crdrisko: That's what I thought too. Looking at this library, all derived classes actually include that function, so it's probably fine to. Nevertheless, I suspect the creator left the base class broad for a reason. Maybe for the sake of the question, we should assume there are other derived classes that don't implement `doSomethingNew()`? – marcman Aug 23 '20 at 16:11
  • @marcman: What you have posted is not C++. It doesn't compile as C++ even partially. There's no `virtual` base class, or base classes of any kind. – Nicol Bolas Aug 23 '20 at 16:18
  • @marcman: "*I want to be able to call the DoSomethingNew() function*" If you took a pointer to the base class, then you *don't* want to be able to call this. That's what it meant when you took a pointer to the base class. – Nicol Bolas Aug 23 '20 at 16:19
  • @NicolBolas I wasn't trying to post code that would compile explicitly....I was trying to ask a question about how to do something. Now if I knew how to do it and was getting some unexplainable error, I would have tried to post something replicable. If I don't even know how to do something, I don't know how I would post the code I was trying to figure out how to write.... Ah with your edited comment I see what you mean. I edited the OP to fix the missed inheritance – marcman Aug 23 '20 at 16:20
  • @marcman: What I'm saying is that the code you're trying to write is *logically incoherent*. It doesn't make sense. If a function takes a pointer to the base class, then that *means* that function can work with *any class* derived from that base class. If the function needs to call a function implemented on a specific derived class that isn't accessible through a base class, then the function's interface is a **lie**. The function *really* takes a derived class, and you should specify that in the interface. – Nicol Bolas Aug 23 '20 at 16:24
  • @NicolBolas: I am somewhat constrained in that sense though, as the library usage expects a pointer to a base class. The idea with this paradigm as far as I understand is so that it can accept any of the derived classes. Isn't that the whole premise of abstract base classes? Of course, I see why it would make sense to have `DoSomethingNew` represented in the base class to then, but I can't make that adjustment in the library. – marcman Aug 23 '20 at 16:27
  • @marcman: "*The idea with this paradigm as far as I understand is so that it can accept any of the derived classes.*" But you're not accepting *any* derived class; you're only accepting a *specific* derived class, one that has this new function in it. "Any" means *any*, not just the set of classes you've written. – Nicol Bolas Aug 23 '20 at 16:28
  • @NicolBolas: I follow. I guess what I'm trying to figure out is how to design this function so it *can* accept specific derived classes. I thought some template specialization with a `dynamic_cast`might be a solution – marcman Aug 23 '20 at 16:30
  • If you’re able to modify the calling function why use a pointer-to-base at all? Couldn’t you just template the function and be done with it? No need for pointers or dynamic casting – crdrisko Aug 23 '20 at 16:41
  • @crdrisko: I can modify the calling function, except that its usage in the library will always be with a pointer-to-base class – marcman Aug 23 '20 at 16:45
  • 1
    This doesn't address the question, but "virtual base class" refers to a base class that is inherited virtually. For example, `class derived : public virtual base {};`. Virtual bases introduce some complications. What you have here is not a virtual base class; it's an abstract class, that is, a class that has one or more pure virtual functions. – Pete Becker Aug 23 '20 at 17:23
  • @PeteBecker: thanks for the clarifiction! I didn't realize there was a difference--I thought they were synonomous. I updated the post accordingly – marcman Aug 23 '20 at 19:11

2 Answers2

1

dynamic_cast<> exists for when you need to either down cast or cross cast from your pointer-to-base into a derived class. In your example it would look something like this:

std::unique_ptr<MyBase> object = std::unique_ptr<MyAlpha>(new MyAlpha);
// ...
dynamic_cast<MyAlpha*>(object.get())->DoSomethingNew();

You can read more about it here, but as I mentioned in my comment, too many of these is an indicator you have a design problem. Especially here when you have that functionality in both derived classes, it could easily be moved into the base class.

As an alternative to dynamic_cast<> since you are unable to modify the base class, you could create your own base class where you inherit from the unmodifiable base class and customize the interface to something you will actually use.

class NewBase : public MyBase
{
public:
    void DoSomething() = 0;
    void DoSomethingNew() = 0;
};

std::unique_ptr<NewBase> object  = std::unique_ptr<MyAlpha>(new MyAlpha);
// ...
object->DoSomethingNew();
crdrisko
  • 454
  • 2
  • 5
  • 14
1

Each of those derived classes has some new functionality not present in the abstract base class, but both derived classes have the same function.

Then capture that in a possibly abstract intermediate class:

class MyMiddle : public MyBase {
    public:
        virtual void DoSomethingNew() = 0;
};

class MyAlpha : public MyMiddle {
    public:
        void DoSomething() override;
        void DoSomethingNew() override;
};

class MyBeta : public MyMiddle {
    public:
        void DoSomething() override;
        void DoSomethingNew() override;
};

This way you can implement the common functionality around DoSomethingNew by referencing MyMiddle, avoiding a lot of code duplication you might otherwise get.

Now I have a templated function somewhere that accepts a pointer (in my case a std::unique_ptr) to the base class, and I want to be able to call the DoSomethingNew() function.

Since you only have a pointer to the base class, the compiler will not out of the box allow you to call methods of a derived class on that. However, if you expect the implementation to actually be an instance of a derived class, you can cast to that.

Use a dynamic_cast to check whether the derived class is of the expected type and use it as that type if it is. Use a static_cast if you are 100% totally absolutely sure that the argument will always be of the derived class, now and forever in the future. In other words, don't. Go for dynamic_cast.

Note that dynamic_cast is available for raw pointers but not for unique_ptr. So you have two options: either keep the unique pointer to base and use a raw pointer to derived for access. Or cast the pointer in an elaborate multi-step procedure. The latter only makes sense if you want to hold on to the pointer for longer in a context where it needs to be of the derived type. The simple case goes like this:

void SomethingSimple(std::unique_ptr<MyBase> base) {
    MyMiddle* derived = dynamic_cast<MyMiddle>(base.get());
    if (derived == nullptr) {
        // derived wasn't of the correct type, recover in a reasonable way.
        return;
    }
    derived->DoSomethingNew();
}

The more complex pointer cast goes like this instead:

void SomethingComplicated(std::unique_ptr<MyBase> base) {
    MyMiddle* derived = dynamic_cast<MyMiddle>(base.get());
    if (derived == nullptr) {
        // derived wasn't of the correct type, recover in a reasonable way.
        return;
    }
    std::unique_ptr<MyMiddle> middle(derived);
    // Here two unique_ptr own the same object, make sure not to throw exceptions!
    base.release();  // Complete transfer of ownership.
    SomethingThatNeedsTheNewFunction(middle);  // Pass ownership of middle type.
}

Of course, std::unique_ptr does allow for custom deleters, which makes this whole setup way more fun. I recommend you read this answer for code that is propagating the deleter while constructing a unique pointer to a derived class. This only becomes necessary if your function signature allows for a non-standard deleter in its pointer argument.

You could do the above without the MyMiddle class, using two separate calls to dynamic_cast to try converting to each of your derived classes in turn. But as long as the middle class and the shared functionality makes sense conceptually I'd go for that. If you did two separate casts, then you could call a template function for both cases, and that template function could assume existence of that function even though it would be operating on different argument types. Doesn't feel like a great solution to me, though.

I feel like template specialization might be the way to go here but I'm not quite sure.

That would work if the caller would call the function with the actual derived type as the static type of the argument. So you could do

template <typename Base_t> void MyOperation(std::unique_ptr<Base_t> &base_object) {
    // Handle the case where DoSomethingNew is not an option.
}
template <> void MyOperation(std::unique_ptr<MyAlpha> &alpha_object) {
    alpha_object->DoSomethingNew();
}
template <> void MyOperation(std::unique_ptr<MyBeta> &beta_object) {
    beta_object->DoSomethingNew();
}

But the following would still not call the specialized function:

    std::unique_ptr<MyBase> object(new MyAlpha());
    MyOperation(object);

Even though object dynamically contains a MyAlpha its static type is a unique pointer to MyBase, and that's what drives the template parameters. So I can't see a way where such a specialization would be useful to you.

MvG
  • 57,380
  • 22
  • 148
  • 276