我正在尝试向现有库添加新功能。我需要将新数据添加到类层次结构中,以便根类具有访问器。任何人都应该能够获得这些数据,只有子类可以设置它(即公共getter和受保护的setter)。
为了保持向后兼容性,我知道我不能执行以下任何操作(列表仅包含与我的问题相关的操作):
我可以想到将这些数据添加到层次结构的两种方法:向根类添加新的成员变量或添加纯虚拟访问器函数(以便数据可以存储在子类中)。但是,为了保持向后的可比性,我不能做其中任何一个。
该库正在广泛使用pimpl成语,但不幸的是我必须修改的根类不使用这个成语。但是,子类使用这个习语。
现在只有我能想到的解决方案是使用静态哈希映射模拟成员变量。所以我可以创建一个静态哈希映射,将这个新成员存储到它,并为它实现静态访问器。像这样的东西(在伪c ++中):
class NewData {...};
class BaseClass
{
protected:
static setNewData(BaseClass* instance, NewData* data)
{
m_mapNewData[instance] = data;
}
static NewData* getNewData(BaseClass* instance)
{
return m_mapNewData[instance];
}
private:
static HashMap<BaseClass*, NewData*> m_mapNewData;
};
class DerivedClass : public BaseClass
{
void doSomething()
{
BaseClass::setNewData(this, new NewData());
}
};
class Outside
{
void doActions(BaseClass* action)
{
NewData* data = BaseClass::getNewData(action);
...
}
};
现在,虽然这个解决方案可能有用,但我发现它非常难看(当然我也可以添加非静态访问器函数,但这不会消除丑陋)。
还有其他解决方案吗?
谢谢。
答案 0 :(得分:3)
您可以使用decorator pattern。装饰器可以公开新的数据元素,并且不需要对现有类进行任何更改。如果客户端通过工厂获取对象,这种方法效果最好,因为这样您就可以透明地添加装饰器。
答案 1 :(得分:3)
最后,使用abi-compliance-checker等自动工具检查二进制兼容性。
答案 2 :(得分:2)
您可以添加导出的函数(declspec导入/导出)而不影响二进制兼容性(确保您不删除任何当前函数并在最后添加新函数),但不能通过添加新数据来增加类的大小成员。
你不能增加类的大小的原因是对于那些使用旧大小编译但使用新扩展类的人来说,意味着数据成员在你的对象中存储在你的对象之后(如果添加多于1个单词)会在新课程结束时被删除。
e.g。
旧:
class CounterEngine {
public:
__declspec(dllexport) int getTotal();
private:
int iTotal; //4 bytes
};
新:
class CounterEngine {
public:
__declspec(dllexport) int getTotal();
__declspec(dllexport) int getMean();
private:
int iTotal; //4 bytes
int iMean; //4 bytes
};
客户可能会:
class ClientOfCounter {
public:
...
private:
CounterEngine iCounter;
int iBlah;
};
在内存中,旧框架中的ClientOfCounter看起来像这样:
ClientOfCounter: iCounter[offset 0],
iBlah[offset 4 bytes]
相同的代码(未重新编译但使用新版本看起来像这样)
ClientOfCounter: iCounter[offset 0],
iBlah[offset 4 bytes]
即。它不知道iCounter现在是8个字节而不是4个字节,所以iBlah实际上被iCounter的最后4个字节所破坏。
如果您有备用私有数据成员,则可以添加Body类来存储任何将来的数据成员。
class CounterEngine {
public:
__declspec(dllexport) int getTotal();
private:
int iTotal; //4 bytes
void* iSpare; //future
};
class CounterEngineBody {
private:
int iMean; //4 bytes
void* iSpare[4]; //save space for future
};
class CounterEngine {
public:
__declspec(dllexport) int getTotal();
__declspec(dllexport) int getMean() { return iBody->iMean; }
private:
int iTotal; //4 bytes
CounterEngineBody* iBody; //now used to extend class with 'body' object
};
答案 3 :(得分:1)
如果您的库是开源的,那么您可以请求将其添加到upstream-tracker。它将自动检查所有库版本以实现向后兼容。因此,您可以轻松维护您的API。
编辑:qt4库的报告为here。
答案 4 :(得分:0)
很难保持二进制兼容性 - 只维护接口兼容性要容易得多。
我认为唯一合理的解决方案是打破支持当前库并将其重新设计为仅为类导出纯虚拟接口。
答案 5 :(得分:-1)
将数据成员添加到根目录将破坏二进制兼容性(并强制重建,如果这是您关心的问题),但它不会破坏向后兼容性,也不会将添加成员函数(虚拟或非虚拟)。添加新的成员函数是显而易见的方法。