10

I have an abstract base class Node, which is derived from an abstract Interface class IObservable. There is a several classes implementing the abstract IObservable: SingleObservable and MultiObservable

I want to create a class ObservableNode, derived from the base Node class and specify on its declaration which class to use for the implementation of the IObservable interface.

I've added using ... statements for every pure virtual method in IObservable, referring to the methods in the implementation classes but I still get errors saying that ObservableNode is an abstract class, missing the implementation of notifyObservers(IObject*).

If I add the parameter IObject* to the using statement I get an "expected ';' before '(' token" error

How can I solve this?

class IObservable {
  public:
    virtual ~IObservable() {};
    virtual void notifyObservers(IObject*) = 0;
};
class SingleObservable: public IObservable {
  public:
    virtual ~SingleObservable() {};
    void notifyObservers(IObject*) override { 
      //some implementaiton
    };
};
class MultiObservable: public IObservable {
  public:
    virtual ~MultiObservable() {};
    void notifyObservers(IObject*) override { 
      //some other implementaiton
    };
};

class Node: public IObservable {
  public:
    virtual ~Node() {};
};

class ObservableNode: public Node, public SingleObservable {
  public:
    virtual ~ObservableNode() {};
    using SingleObservable::notifyObservers;
    // using SingleObservable::notifyObservers(IObject*); // expected ';' before '(' token error

};


Node* node = new ObservableNode() // instantiating abstract class error, missing notifyObservers(IObject*) implementation
Bascy
  • 2,017
  • 1
  • 21
  • 46

3 Answers3

7

Your problem seems to be that you inherit Node which is still abstract, and also causes to introduce the good old multimple inheritance vicious diamond problem. When I change your code like this, the error disappears:

class Node: public IObservable {
  public:
    virtual ~Node() {};
    // ** Added an implementation here **
    void notifyObservers(IObject*) override { 
          //some other implementaiton
    };
};

class ObservableNode: public virtual Node, public virtual SingleObservable {
                          // ^^^^^^^              ^^^^^^^
  public:
    virtual ~ObservableNode() {};
    using SingleObservable::notifyObservers;
};

int main() {
    Node* node = new ObservableNode();
}

See it live on coliru.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • @Scheff THX adopted it. Not to inherit `Node` from `IObservable` would be certainly an alternative solution. You may still post that as an answer. – πάντα ῥεῖ Apr 28 '19 at 13:20
  • That indeed solved the problem, thanks! And indeed, I do want the IObservable interface in the Node definition – Bascy Apr 28 '19 at 13:21
  • 1
    Note that in this particular example, `Node* node = new ObservableNode();` will mean `node->notifyObservers(obj)` will invoke `Node::notifyObservers(IObject*)` and not `SingleObservable::notifyObservers(IObject*)`, which might be unexpected, considering we instantiate an `ObservableNode` object which specifies `using SingleObservable::notifyObservers;`. What OP might possible want is to, in `ObservableNode`, define an `notifyObservers(IObject*)` override which explictly forwards to `SingleObservable::notifyObservers(IObject*)`. – dfrib Apr 28 '19 at 13:22
  • @Bascy You should be aware of that previous comment. You're still struggling with the diamond ambiguity problem, and might get unexpected results. – πάντα ῥεῖ Apr 28 '19 at 13:24
  • 1
    ... then a call `node->notifyObservers(obj)` will go down the vtable to `ObservableNode::notifyObservers(IObject*)`, and from there explicitly up to (by forwarding) `SingleObservable::notifyObservers(IObject*)`. – dfrib Apr 28 '19 at 13:25
3

@πάντα ῥεῖ's answer describe one workaround, but possible this is not what OP is after here. Also, as my comment describe under the answer, the approach in the answer might give unexpected results e.g. when invoking node->notifyObservers(obj):

Note that in this particular example, Node* node = new ObservableNode(); will mean node->notifyObservers(obj) will invoke Node::notifyObservers(IObject*) and not SingleObservable::notifyObservers(IObject*), which might be unexpected, considering we instantiate an ObservableNode object which specifies using SingleObservable::notifyObservers;.

In OP's original code, we are suffering from multiple inheritance ambiguity, as we are not using virtual inheritance when Node and SingleObservable (and MultiObservable) derives from IObservable:

class SingleObservable: public IObservable {
  public:
    virtual ~SingleObservable() {};
    void notifyObservers(IObject*) override { 
      //some implementaiton
    };
};

class Node: public IObservable {
  public:
    virtual ~Node() {};
};

Meaning our the object's memory layout, w.r.t. inheritance, of ObservableNode to looks like the following

 IObservable  IObservable
           |  |
        Node  SingleObservable
           \  /
       ObservableNode

whereas, in this context, we are likely to want an object's memory layout looking as follows

       IObservable
           /  \
        Node  SingleObservable
           \  /
       ObservableNode

If we were to correct this, Node can stay abstract, and a call to node->notifyObservers(obj) with node as OP's example will result in invocation of SingleObservable::notifyObservers, as might have been expected.

class Node: public virtual IObservable {
                // ↑↑↑↑↑↑↑
  public:
    virtual ~Node() {};
};

class SingleObservable: public virtual IObservable {
                            // ↑↑↑↑↑↑↑
  public:
    virtual ~SingleObservable() {};
    void notifyObservers(IObject*) override { 
        std::cout << "SingleObservable::notifyObservers";
    };
};

struct DummyObj : public IObject {};

int main() {
    Node* node = new ObservableNode();
    DummyObj obj;
    node->notifyObservers(obj);  // SingleObservable::notifyObservers
}

Note that we not need virtual inheritance for when ObservableNode derives from Node and SingleObservable.

Finally, if we'd want Node be non-abstract (specifically, to provide an override of void notifyObservers(IObject*)), then ObservableNode must provide it's own (final) override of it, as we will otherwise inherit two final overrides of it in ObservableNode (one from Node and one from SingleObservable). In this case, ObservableNode could simply define its own override which explicitly calls the base class of choice, e.g.

class Node: public virtual IObservable {
  public:
    virtual ~Node() {};
    void notifyObservers(IObject*) override { 
        std::cout << "Node::notifyObservers";
    };
};

class SingleObservable: public virtual IObservable {
  public:
    virtual ~SingleObservable() {};
    void notifyObservers(IObject*) override { 
        std::cout << "SingleObservable::notifyObservers";
    };
};

class ObservableNode: public Node, public SingleObservable {
  public:
    virtual ~ObservableNode() {};
    // Non-ambiguous final override in ObservableNode.
    // We could use `override` specifier here, but we might as well
    // use `final`, if we are not expecting something to derive from ObservableNode.
    void notifyObservers(IObject* obj) final { 
        SingleObservable::notifyObservers(obj);
    };
};

struct DummyObj : public IObject {};

int main() {
    Node* node = new ObservableNode();
    DummyObj obj;
    node->notifyObservers(obj);  // SingleObservable::notifyObservers
}

See ISO C++ FAQ - Inheritance — Multiple and Virtual Inheritance for details on the diamond inheritance structure and virtual inheritance.

dfrib
  • 70,367
  • 12
  • 127
  • 192
0

Thanks for all the suggestions! Implementing those caused a lot of other problems with other classes already using the Observable implementations, so I opted to solve it in a different manner, by including an implementation instance and delegating all methods to that one

class IObject {
    public:
    virtual ~IObject() {};
};

class IObservable {
  public:
    virtual ~IObservable() {};
    virtual void notifyObservers(IObject*) = 0;
};

class SingleObservable: public IObservable {
  public:
    virtual ~SingleObservable() {};
    void notifyObservers(IObject*) override { 
        std::cout << "Single\n";
      //some implementaiton
    };
};

class MultiObservable: public IObservable {
  public:
    virtual ~MultiObservable() {};
    void notifyObservers(IObject*) override { 
        std::cout << "Multi\n";
      //some other implementaiton
    };
};

class Node: public IObservable {
  public:
    virtual ~Node() {};
    // void notifyObservers(IObject*) override { };
};

class SingleObservableNode: public Node {
  public:
    SingleObservableNode() {};
    virtual ~SingleObservableNode() {
        delete obs;
    };
    void notifyObservers(IObject* obj) override { 
        obs->notifyObservers(obj);
    }
    private:
     IObservable* obs = new SingleObservable();
};


class MultiObservableNode: public Node {
  public:
    MultiObservableNode() {};
    virtual ~MultiObservableNode() {
        delete obs;
    };
    void notifyObservers(IObject* obj) override { 
        obs->notifyObservers(obj);
    }
    private:
     IObservable* obs = new MultiObservable();
};

Node* node1 = new SingleObservableNode(); 
Node* node2 = new MultiObservableNode(); 

int main()
{
    node1->notifyObservers(nullptr);   // "Single"
    node2->notifyObservers(nullptr);   // "Multi"
   return 0;
}
Bascy
  • 2,017
  • 1
  • 21
  • 46