具有多个继承的对象共享一种资源-寻找良好的设计模式

时间:2018-08-15 16:55:44

标签: c++ design-patterns microcontroller

希望这之前没有得到回答,我发现很难找到关于我的问题的简要描述。

我将要编写一个C ++ API,该API应该在微控制器以及PC目标上进行编译,以抽象化与某些硬件设备的通信。设备的操作模式以及要控制的参数可能会在运行时更改,而连接保持不变。该连接由单独的类管理,我的基类实例对其具有受保护的引用。基本设备如下所示(简化示例):

class DeviceBase
{
public:
    void setOnOffState (bool onOff);
    bool getOnOffState();
protected:
    DeviceBase (Connection& c);
    Connection& connection;
}

DeviceBase::DeciveBase (Connection& c) : connection (c) {};
void DeviceBase::setOnOffState (bool onOff) {connection.sendParameter (/* Parameter number for onOff */, onOff); };
bool DeviceBase::getOnPffState() {return connection.requestParameter (/* Parameter number for onOff */); };

现在,有些通用设备类型都共享一个基本参数集。假设存在通用类型1,该类型始终具有parameterA和parameterB,以及通用类型2的其始终具有parameterC和parameterD。因此,它们的实现如下所示:

class GenericDeviceType1 : public DeviceBase
{
public:
    void setParameterA (int parameterA);
    int getParameterA();
    void setParameterB (char parameterB);
    char getParameterB();
protected:
    GenericDeviceType1 (Connection& c);
}

GenericDeviceType1::GenericDeviceType1 (Connection& c) : DeviceBase (c) {};
void GenericDeviceType1::setParameterA (int parameterA) {connection.sendParameter (/* Parameter number for parameterA */, parameterA); };
int  GenericDeviceType1::getParameterA() {return connection.requestParameter (/* Parameter number for parameterA */); };
//... and so on - I think you got the principle

但是它变得更加复杂。每种类型都有特定的口味。但是有些共享一些参数组。现在,我理想地要做的是使用这样的多重继承构建它们:

class DeviceType1ParameterSetX // a device with parameters E and F
{
public:
    void setParameterE (float parameterE);
    float getParameterE();
    void setParameterF (int parameterF);
    int getParameterF();
}

class DeviceType1ParameterSetY // a device with parameters G and H
{
public:
    void setParameterG (bool parameterG);
    bool getParameterG();
    void setParameterH (char parameterH);
    char getParameterH();
}

class DeviceType1ParameterSetZ // a device with parameters I and J
{
public:
    void setParameterI (int parameterI);
    int getParameterI();
    void setParameterJ (int parameterJ);
    int getParameterJ();
}

class SpecificDeviceType11 : public GenericDeviceType1,
                             public DeviceType1ParameterSetX,
                             public DeviceType1ParameterSetZ
{
public:
    SpecificDeviceType11 (Connection &c);
    //...
}

class SpecificDeviceType12 : public GenericDeviceType1,
                             public DeviceType1ParameterSetX,
                             public DeviceType1ParameterSetY,
                             public DeviceType1ParameterSetZ
{
public:
    SpecificDeviceType12 (Connection &c);
    //...
}

此方法的问题:类DeviceTypeNParameterSetM对连接一无所知,因此无法直接实现调用连接实例的setter和getter函数。但是,我真的想避免公开基类的连接成员以保持api的清洁。我知道我可以在每个参数集类中存储对连接的引用,但是这对于我来说似乎是浪费内存,因为它应该可以在内存占用较小的微控制器上运行,并且没有可能动态内存管理。因此,理想情况下,每个特定实例的内存占用量应该相同。

现在我的问题是:产生干净的公共API的解决方案如何?我期待着一些灵感!附带说明:最终会有大约150种不同的特定设备口味,所以我真的很想让它尽可能有条理和用户友好!

2 个答案:

答案 0 :(得分:1)

执行此操作的通常方法是使DeviceBase基类public virtual成为所有需要了解它的所有ParameterSet类的public virtual基类。然后他们中的任何一个都可以访问连接。

当您使用这种虚拟继承时,需要在每个非抽象类的构造函数中显式初始化DeviceBase基类,但这并不太困难。

答案 1 :(得分:0)

我的第一次尝试实际上是次优的(如果您对此感兴趣,请参阅编辑历史记录)。实际上,不可能进行多重继承并使派生类的大小与“真实”基类相同,因为每个父类都必须具有一个不同的地址(即使除一个以外的其他所有地址) 父类为空)。

您可以改为使用尾式继承:

struct Connection {
    template<class T>
    void sendParameter(int,T); // implemented somewhere
    template<class T>
    T requestParameter(int);   // implemented somewhere
};

class DeviceBase {
public:
    void setOnOffState(bool onOff) { connection.sendParameter(0, onOff); }
    bool getOnOffState()           { return connection.requestParameter<bool>(0); }
protected:
    DeviceBase(Connection& c) : connection(c) {}
    template<class T>
    void sendParameter(int i,T t) { connection.sendParameter(i,t); }
    template<class T>
    T requestParameter(int i) { return connection.requestParameter<T>(i); }
private:
    Connection& connection;
};

template<class Base>
class DeviceType1ParameterSetX : public Base // a device with parameters A and B
{
public:
    void setParameterA (float parameterA) { this->sendParameter(0xA, parameterA);}
    float getParameterA()                 { return  this->template requestParameter<float>(0xA);}
    void setParameterB (int parameterB)   { this->sendParameter(0xB, parameterB);}
    int getParameterB()                   { return  this->template requestParameter<int>(0xB);}

    DeviceType1ParameterSetX(Connection& c) : Base(c) {}

};

template<class Base>
class DeviceType1ParameterSetY : public Base // a device with parameters C and D
{
public:
    void setParameterC (float parameterC) { this->sendParameter(0xC, parameterC);}
    float getParameterC()                 { return  this->template requestParameter<float>(0xC);}
    void setParameterD (int parameterD)   { this->sendParameter(0xD, parameterD);}
    int getParameterD()                   { return  this->template requestParameter<int>(0xD);}
    DeviceType1ParameterSetY(Connection& c) : Base(c) {}
};

template<class Base>
class DeviceType1ParameterSetZ : public Base // a device with parameters E and F
{
public:
    void setParameterE (float parameterE) { this->sendParameter(0xE, parameterE);}
    float getParameterE()                 { return  this->template requestParameter<float>(0xE);}
    void setParameterF (int parameterF)   { this->sendParameter(0xF, parameterF);}
    int getParameterF()                   { return  this->template requestParameter<int>(0xF);}
    DeviceType1ParameterSetZ(Connection& c) : Base(c) {}

};

class SpecificDeviceTypeXZ : public 
        DeviceType1ParameterSetX<
        DeviceType1ParameterSetZ<
            DeviceBase> >
{
public:
    SpecificDeviceTypeXZ (Connection &c) : DeviceType1ParameterSetX(c) {}
    //...
};


class SpecificDeviceTypeXY : public 
        DeviceType1ParameterSetX<
        DeviceType1ParameterSetY<
            DeviceBase> >
{
public:
    SpecificDeviceTypeXY (Connection &c) : DeviceType1ParameterSetX(c) {}
    //...
};

void foo(Connection& c)
{
    SpecificDeviceTypeXY xy(c);
    SpecificDeviceTypeXZ xz(c);
    static_assert(sizeof(xy)==sizeof(void*), "xy must just contain a reference");
    static_assert(sizeof(xz)==sizeof(void*), "xz must just contain a reference");
    xy.setOnOffState(true);
    xy.setParameterC(1.0f);
    xz.setParameterF(xy.getParameterB());
}

我在某种程度上简化了您的示例,以节省键入内容(例如,我省略了GenericDeviceType1,在我的示例中实际上是DeviceType1ParameterSetX<DeviceBase>),并且名称/数字与您的示例不匹配。

在这里玩耍是一个Godbolt-Link(确认大小不会增加):https://godbolt.org/z/BtNOe_ 在这里,rdi将保留第一个指针参数(大多数情况下是隐式的this参数),或Connection& c的{​​{1}}参数隐含的指针。 foo将始终保存参数编号esi(因为它是i方法的第一个整数参数,并且取决于类型,{{1}的下一个参数}调用)将通过ConnectionsendParameter传递,返回值将在xmm0中用于整数,在edx中用于浮点数(所有假定x86_64bit ABI)。

要了解会发生什么,我还建议在多个位置插入一些调试输出(例如eax)。

在任何阶段添加数据成员(或方法)都应该保存(当然会增加大小)。