Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strategy for calling functions in classes with diamond inheritance and virtual base classes

If we have diamond inheritance and use public virtual base classes, we can stop the first constructor from being called multiple times. Now, I'd like to do the same sort of thing for functions outside of the constructor. For example, the code:

#include <iostream>

struct A {
    virtual void foo() {
        std::cout << "A" << std::endl;
    }
};
struct B : virtual public A {
    virtual void foo() {
        A::foo();
        std::cout << "B" << std::endl;
    }
};
struct C : virtual public A {
    virtual void foo() {
        A::foo();
        std::cout << "C" << std::endl;
    }
};
struct D : public B, public C{
    virtual void foo() {
        B::foo();
        C::foo();
        std::cout << "D" << std::endl;
    }
};

int main() {
    D d;
    d.foo();
}

produces the result

A
B
A
C
D

I'd like to modify it so that it just produces

A
B
C
D

What sort of strategies or patterns accomplish this?

EDIT 1

I like Tony D's answer better than the following. Nonetheless, it's in theory possible to use constructors of another class in order to define the proper hierarchy of functions. Specifically

#include <iostream>

struct A;
struct B;
struct C;
struct D;

namespace foo {
    struct A {
        A(::A* self);
    };
    struct B : virtual public A {
        B(::B* self);
    };
    struct C : virtual public A {
        C(::C* self);
    };
    struct D : public B, public C{
        D(::D* self);
    };
}

struct A {
private:
    friend class foo::A;
    friend class foo::B;
    friend class foo::C;
    friend class foo::D;
    int data;
public:
    A() : data(0) {}
    virtual void foo() {
        (foo::A(this));
    }
    void printme() {
        std::cout << data << std::endl;
    }
};
struct B : virtual public A {
    virtual void foo() {
        (foo::B(this));
    }
};
struct C : virtual public A {
    virtual void foo() {
        (foo::C(this));
    }
};
struct D : public B, public C{
    virtual void foo() {
        (foo::D(this));
    }
};

foo::A::A(::A* self) {
    self->data+=1;
    std::cout << "A" << std::endl;
}
foo::B::B(::B* self) : A(self) {
    self->data+=2;
    std::cout << "B" << std::endl;
}
foo::C::C(::C* self) : A(self) {
    self->data+=4;
    std::cout << "C" << std::endl;
}
foo::D::D(::D* self) : A(self), B(self), C(self) {
    self->data+=8;
    std::cout << "D" << std::endl;
}

int main() {
    D d;
    d.foo();
    d.printme();
}

Basically, the classes inside of the namespace foo do the computation for the function named foo. This seems a little verbose, so perhaps there's a better way to do it.

EDIT 2

Thanks again to Tony D for clarifying the above example. Yes, essentially what the above does is create temporary variables that adhere to the virtual base designation. In this way, we can use the constructor in order to prevent redundant computations. The extra cruft was to try and show how to get access to access to private members that may have been buried in the base class. Thinking about it a little bit more, there's another way to do this, which may or may not be cleaner depending on the application. I'll leave it here for reference. As with the last example, the weakness is that we're essentially required to wire the the inheritance again, by hand.

#include <iostream>

struct A {
protected:
    int data;
public:
    A() : data(0) {}
    struct foo{
        foo(A & self) {
            self.data+=1;
            std::cout << "A" << std::endl;
        }
    };
    void printme() {
        std::cout << data << std::endl;
    }
};
struct B : virtual public A {
    struct foo : virtual public A::foo {
        foo(B & self) : A::foo(self) {
            self.data+=2;
            std::cout << "B" << std::endl;
        }
    };
};
struct C : virtual public A {
    struct foo : virtual public A::foo {
        foo(C & self) : A::foo(self) {
            self.data+=4;
            std::cout << "C" << std::endl;
        }
    };
};
struct D : public B, public C{
    struct foo : public B::foo, public C::foo {
        foo(D & self) : A::foo(self) , B::foo(self), C::foo(self) {
            self.data+=8;
            std::cout << "D" << std::endl;
        }
    };
};

int main() {
    D d;
    (D::foo(d));
    d.printme();
}

Essentially, the call (D::foo(d)) creates a temporary who's constructor does the actions we desire. We pass in the object d by hand in order to access to the memory. Since the classes foo are inside of the classes A..D, this gives us access to the protected members.

like image 728
wyer33 Avatar asked Oct 20 '22 18:10

wyer33


1 Answers

Just an implementation of polkadotcadaver's idea. Here, Limiter is designed to be a reusable mechanism for this, and the virtual base class should have a member of that type. The controlled base-class function uses bool Limiter::do_next() to ask whether it should run "as usual" or return immediately, while the derived classes calling the base-class function get a scope-guard object from the limiter that takes ownership if not already claimed, and releases any ownership it had on destruction.

#include <iostream>

class Limiter
{
  public:
    Limiter() : state_(Unlimited) { }

    class Scope
    {
      public:
        Scope(Limiter& l)
          : p_(l.state_ == Unlimited ? &l : NULL)
        { if (p_) p_->state_ = Do_Next; }

        ~Scope() { if (p_) p_->state_ = Unlimited; }
      private:
        Limiter* p_;
    };

    Scope get() { return Scope(*this); }

    bool do_next()
    {
        if (state_ == Do_Next) { state_ = Suspended; return true; }
        return state_ != Suspended;
    }

  private:
    enum State { Unlimited, Do_Next, Suspended } state_;
};

struct A {
    Limiter limiter_;
    virtual void foo() {
        if (limiter_.do_next())
            std::cout << "A" << std::endl;
    }
};
struct B : virtual public A {
    virtual void foo() {
        Limiter::Scope ls = A::limiter_.get();
        A::foo();
        std::cout << "B" << std::endl;
    }
};
struct C : virtual public A {
    virtual void foo() {
        Limiter::Scope ls = A::limiter_.get();
        A::foo();
        std::cout << "C" << std::endl;
    }
};

struct D : public B, public C{
    virtual void foo() {
        Limiter::Scope ls = A::limiter_.get();
        B::foo();
        C::foo();
        std::cout << "D" << std::endl;
    }
};

int main() {
    D d;
    d.foo();
}

Discussion of technique edited into your question

Took me a while to work out what you were doing in your code ;-P - so for the sake of discussion I'll post what I boiled it down to:

#include <iostream>

namespace foo {
    struct A {
        A() { std::cout << "A\n"; }
    };
    struct B : virtual public A {
        B() { std::cout << "B\n"; }
    };
    struct C : virtual public A {
        C() { std::cout << "C\n"; }
    };
    struct D : public B, public C{
        D() { std::cout << "D\n"; }
    };
}

struct A { virtual void foo() { foo::A(); } };
struct B : virtual public A { void foo() { foo::B(); } };
struct C : virtual public A { void foo() { foo::C(); } };
struct D : public B, public C { void foo() { foo::D(); } };

int main() {
    D d;
    d.foo();
}

For others' sake - this works by having the A..D::foo() functions create temporary objects of types foo::A..D, the constructors for which honour the virtual base designation so foo::A::A() is only called once.

As a general solution, an issue with this is that you have to manually synchronise the foo:: structures, so there's redundancy and fragility. It's clever though!

like image 180
Tony Delroy Avatar answered Nov 04 '22 02:11

Tony Delroy