Using RTTI with objects derived from an external library
Asked Answered
S

1

6

I am working with an external library and need to create an observer pattern where observers are derived from an object that belongs to the library. I do not want to change the base class from the library and at the same time I have to use the list of references/pointers to this unchangeable base class. On top of this the library constructs the list of objects from which I need to sift the ones that are fit to be observers.

The code I wrote is roughly equivalent to this:

#include <iostream>
#include <vector>
#include <memory>

// This class is from an external library which I don't want to chagne
class BaseFromLibrary {
  public:
    virtual ~BaseFromLibrary() {}
};

class BaseOfObserver {
  public:
    void notify() { std::cout << "What-ho!\n"; };
};

class Observer : public BaseFromLibrary, public BaseOfObserver {};

class Subject {
  public:
    std::vector<std::shared_ptr<Observer>> observers;
    void notifyObervers() {
        for (auto &o : observers)
            (*o).notify();
    }
};

int main() {
    // This list is constructed by the library and I cannot interfere with that
    // process
    std::vector<std::shared_ptr<BaseFromLibrary>> list{
        std::make_shared<BaseFromLibrary>(), std::make_shared<Observer>()};
    Subject s;

    for (auto &e : list)
        if (std::dynamic_pointer_cast<Observer>(e) != nullptr)
            s.observers.push_back(std::dynamic_pointer_cast<Observer>(e));

    s.notifyObervers();
}

Then I use the BaseOfObserver to add "subject awareness" to my other derived types. This way I do not have to repeat an if-statement for each specific observer I want to implement.

It seems to work fine, but is that a design mistake? Is there a better way to construct the observer list without RTTI mechanism and without interfering with the library classes?

Sharonsharona answered 11/1, 2016 at 12:38 Comment(0)
W
0

Your problem boils down to having a sequence of base pointers, and wanting to use only such elements of the sequence that are a particular derived type.

dynamic_cast certainly works, but in this case, it's not necessary. If you were to keep the desired pointers in a separate container, you wouldn't need to look for them in a container that contains other pointers as well.

std::vector<Observer *> observers {new Observer()};

You can still have a container with all pointers by copying the observer pointers.

std::vector<BaseFromLibrary *> all {new BaseFromLibrary()};
all.reserve(all.size() + observers.size());
all.insert(all.end(), observers.begin(), observers.end());

Alternatively, you could use something like boost::join from Boost.Range if you want to iterate over all elements in separate containers without copying.

You can trivially copy the observer pointers without dynamic_cast.

s.observers.insert(observers.begin(), observers.end());

Of course, if that's all you're going to do, then you could have used s.observers as the original container for observers in the first place.

As a sidenote, your example program leaks memory. Avoid manual memory management to prevent those situations.

Wheresoever answered 11/1, 2016 at 13:18 Comment(1)
If I understand you correctly, you're suggesting to split list from my example. I may have forgotten to emphasize that this is also something I cannot do. The library constructs that list on its own and I do not want to interfere with that process. I'll try to edit that. Point taken about memory leak.Sharonsharona

© 2022 - 2024 — McMap. All rights reserved.