希望这之前没有得到回答,我发现很难找到关于我的问题的简要描述。
我将要编写一个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种不同的特定设备口味,所以我真的很想让它尽可能有条理和用户友好!
答案 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}的下一个参数}调用)将通过Connection
或sendParameter
传递,返回值将在xmm0
中用于整数,在edx
中用于浮点数(所有假定x86_64bit ABI)。
要了解会发生什么,我还建议在多个位置插入一些调试输出(例如eax
)。
在任何阶段添加数据成员(或方法)都应该保存(当然会增加大小)。