具有多个继承共享一个资源的对象 - 寻找良好的设计模式
Object with multiple inheritance sharing one resource - looking for good design pattern
希望这以前没有被回答过,我发现很难找到对我的问题的快速描述。
我即将编写一个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 始终具有参数 A 和参数 B,泛型类型 2 始终具有参数 C 和参数 D。因此,它们的实现可能如下所示:
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 种不同的特定设备风格,所以我真的很想让它尽可能有条理和用户友好!
执行此操作的常用方法是使 DeviceBase 基类public virtual
,并将其作为需要了解它的所有各种 ParameterSet 类的public virtual
基类包含在内。 然后,如果需要,他们中的任何一个都可以访问连接。
当你像这样使用虚拟继承时,你需要在每个非抽象类的构造函数中显式初始化DeviceBase
基类,但这并不太困难。
我的第一次尝试实际上是次优的(如果您对此感兴趣,请参阅编辑历史记录(。事实上,不可能进行多重继承并保持派生类的大小与"真正的"基类相同,因为每个父类都必须具有不同的地址(即使除了一个之外的所有地址( 父类为空(。
您可以改用尾部继承,如下所示:
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
参数(,或由foo
的Connection& c
参数隐含的指针。esi
将始终i
保存参数编号(因为它是Connection
方法的第一个整数参数,并且根据类型,下一个参数(sendParameter
调用(将通过xmm0
或edx
传递。对于整数,返回值将以eax
为单位,对于浮点数,返回值将以xmm0
为单位(这都是假设x86_64bit ABI(。
为了了解会发生什么,我还建议在几个地方插入一些调试输出(如cout << __PRETTY_FUNCTION__ << ' ' << this << 'n';
(。
在任何阶段添加数据成员(或方法(都应该保存(当然它会增加大小(。
- 什么时候调用组成单元对象的析构函数
- 对RValue对象调用的LValue ref限定成员函数
- CMake-按正确顺序将项目与C运行时对象文件链接
- 空基优化子对象的地址
- 将对象数组的引用传递给函数
- QT在错误的班级中寻找空位
- 你能重载对象变量名本身返回的内容吗
- C++使用整数的压缩数组初始化对象
- 找不到成员对象:没有名为get_event()的成员,也处理多态性和向量
- 将对象移动到std::shared_ptr
- 代理对象的常量正确性
- 提升 ASIO 无法识别计时器对象
- 将Ref对象作为类成员
- 将包含C样式数组的对象初始化为成员变量(C++)
- 如何返回一个类的两个对象相加的结果
- 使用std::函数映射对象方法
- 具有多个继承共享一个资源的对象 - 寻找良好的设计模式
- 使用std::sort与自定义排序对象和数据类型模板-寻找正确的语法
- 我正在寻找一种在从模型中删除并重新添加修改后的可提取对象后操作 iloextract 对象的方法
- 寻找连接对象的字符串表示的惯用方法