简体   繁体   中英

C++ multiple inheritance from base classes with members with same name

Let me tell you the problem I have. I'm designing a set of classes to control a digital device. This device can work in two modes of operation. In the first mode it can perform a specific set of operations, and in the second mode it can perform another set of operations (with possibly some common operations between the two). I can also change the mode of the device on the run, so I can swap between the two modes if necessary. Independently of the mode, the device use the same set of registers.

I was thinking in solve this problem with one base class for each mode, so I can have objects of mode 1 when I need the first set of operations and objects of mode 2 when I need the second set of operations. Then I could derive a class from these two base classes, so I can have objects that perform all the operations.

The problem with my design is that the two base classes have some common functions and references to the same registers. Since I can't prevent inheritance of members I would have duplicates in the derived class. I know I can choose which duplicate to access with the scope operator, but I still think this a bad design.

So my question is: is there an idiomatic way of solve this problem?

If there isn't a right or easy way of solving this, I'm thinking about design 3 hierarchically independently classes. I would have some duplicate code, but that is not a big problem, right?

Code below (simplified) for illustration:

class mode1
{
protected:
    volatile uint8_t& reg1;
    volatile uint8_t& reg2;
    uint8_t data; 
public:
    virtual void operation1() final { // do something }
    virtual void operation2() final { // do something }
    virtual void operation3() final { // do something } 
};


class mode2
{
protected:
    volatile uint8_t& reg1;
    volatile uint8_t& reg2;
    uint8_t data; 
public:
    virtual void operation4() final { // do something }
    virtual void operation2() final { // do something }
    virtual void operation5() final { // do something } 
};


class mode1and2 : public mode1, public mode2
{
public:
    void operation6() { // do something }
    void operation7() { // do something }
};

Note modes 1 and 2 have operation2 and all the data members in common.

The state design pattern looks like a good candidate for your case.
As a minimal, working example:

#include<memory>
#include<iostream>

struct Behavior {
    virtual void f() = 0;
    virtual void g() = 0;
};

struct NullBehavior: Behavior {
    void f() override {}
    void g() override {}
};

struct Mode1: Behavior {
    void f() override { std::cout << "mode 1 - f" << std::endl; }
    void g() override { std::cout << "mode 1 - g" << std::endl; }
};

struct Mode2: Behavior {
    void f() override { std::cout << "mode 2 - f" << std::endl; }
    void g() override { std::cout << "mode 2 - g" << std::endl; }
};

struct Device {
    template<typename B>
    void set() { behavior = std::unique_ptr<Behavior>{new B}; }

    void f() { behavior->f(); }
    void g() { behavior->g(); }

private:
    std::unique_ptr<Behavior> behavior{new NullBehavior};
};

int main() {
    Device device;
    device.f();
    device.g();

    device.set<Mode1>();
    device.f();
    device.g();

    device.set<Mode2>();
    device.f();
    device.g();
}

From the point of view of the user of the device, it doesn't matter what's the mode you are using. Anyway, as requested, you can dynamically change it whenever you want and your device will start to work with the new mode from that point on.
Preferring composition over inheritance solves the issue due the conflicting names. Delegating everything from the outer class to the inner state does the rest.

Note that, if you want to share methods between states, nothing prevents you from putting them in the base class.

A slightly different version helps you sharing also data between the twos:

struct Data {
    volatile uint8_t& reg1;
    volatile uint8_t& reg2;
    uint8_t data;
};

struct Behavior {
    virtual void f(Data &) = 0;
    virtual void g(Data &) = 0;
};

struct NullBehavior: Behavior {
    void f(Data &) override {}
    void g(Data &) override {}
};

struct Mode1: Behavior {
    void f(Data &) override { /* ... */ }
    void g(Data &) override { /* ... */ }
};

 struct Mode2: Behavior {
    void f(Data &) override { /* ... */ }
    void g(Data &) override { /* ... */ }
};

struct Device {
    template<typename B>
    void set() { behavior = std::unique_ptr<Behavior>{new B}; }

    void f() { behavior->f(data); }
    void g() { behavior->g(data); }

private:
    Data data{};
    std::unique_ptr<Behavior> behavior{new NullBehavior};
};

All those parameters that are unique for a specific mode can be part of the class definition or put within Data and ignored if you are working in a different mode.

I'd put the common parts of mode1 and mode2 in a common base class, let's say Common , comprising then your data and member function operation2 . Then, together with virtual inheritance, you can have two views on the same data, even at the same time if needed.

class common {
    friend class mode1;
    friend class mode2;
protected:
    volatile uint8_t& reg1;
    volatile uint8_t& reg2;
    uint8_t data;

public:
    virtual void operation2() final { // do something
    };

};

class mode1 : public virtual common
{
public:
    virtual void operation1() final { // do something
    };
    virtual void operation3() final { // do something }
    };
};

class mode2 : public virtual common
{
public:
    virtual void operation4() final { // do something
    }
    virtual void operation5() final { // do something
    }
};


class mode1and2 : public mode1, public mode2
{
public:
    void operation6() { // do something }
    };
    void operation7() { // do something }
    };
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM