在具有钻石继承和虚拟基类的类中调用函数的策略

时间:2013-12-18 00:23:42

标签: c++ inheritance c++11

如果我们有钻石继承并使用公共虚拟基类,我们可以阻止多次调用第一个构造函数。现在,我想对构造函数之外的函数做同样的事情。例如,代码:

#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();
}

生成结果

A
B
A
C
D

我想修改它以便它只生成

A
B
C
D

什么样的策略或模式可以实现这一目标?

编辑1

我比Tony D的回答更好。尽管如此,理论上可以使用另一个类的构造函数来定义适当的函数层次结构。具体地

#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();
}

基本上,名称空间foo中的类对名为foo的函数进行计算。这似乎有点冗长,所以也许有更好的方法来做到这一点。

编辑2

再次感谢Tony D澄清上述例子。是的,基本上上面的做法是创建符合虚拟基本名称的临时变量。通过这种方式,我们可以使用构造函数来防止冗余计算。额外的尝试是试图展示如何访问可能已被埋在基类中的私人成员。稍微考虑一下,还有另一种方法可以做到这一点,取决于应用程序可能会更清晰,也可能不会更清洁。我会留在这里供参考。与上一个例子一样,缺点是我们基本上需要手动再次连接继承。

#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();
}

本质上,调用(D :: foo(d))创建一个临时的构造函数来执行我们想要的操作。我们手动传入对象d以访问内存。由于类foo在A..D类中,因此我们可以访问受保护的成员。

1 个答案:

答案 0 :(得分:3)

只是polkadotcadaver的想法的实现。这里,Limiter被设计为可重用的机制,虚拟基类应该具有该类型的成员。受控的基类函数使用bool Limiter::do_next()来询问它是否应该“像往常一样”运行或立即返回,而调用基类函数的派生类从限制器获取范围保护对象,如果不是已经声明,并释放它在销毁时的所有权。

#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();
}

讨论编入问题的技术

花了一些时间来弄清楚你在代码中做了些什么;-P - 所以为了讨论起见,我会发布我把它归结为:

#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();
}

为了他人的缘故 - 这可以让A..D::foo()函数创建类型为foo::A..D的临时对象,其构造函数为virtual基本名称,foo::A::A()foo::只召唤一次。

作为一般解决方案,问题在于您必须手动同步{{1}}结构,因此存在冗余和脆弱性。虽然很聪明!