扩展类并保持二进制向后兼容性
我正在尝试向现有库添加新功能。我需要将新数据添加到类层次结构中,以便根类拥有它的访问器。任何人都应该能够获取此数据,只有子类可以设置它(即公共 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);
...
}
};
现在,虽然这个解决方案可能有效,但我发现它非常丑陋(当然我也可以添加非静态访问器函数,但这不会消除丑陋)。
还有其他解决方案吗?
谢谢。
I'm trying to add new functionality to an existing library. I would need to add new data to a class hierarchy so that the root class would have accessors for it. Anyone should be able to get this data only sub-classes could set it (i.e. public getter and protected setter).
To maintain backward compatibility, I know I must not do any of the following (list only includes actions relevant to my problem):
- Add or remove virtual functions
- Add or remove member variables
- Change type of existing member variable
- Change signature of existing function
I can think of two ways to add this data to hierarchy: adding a new member variable to root class or adding pure virtual accessor functions (so that data could be stored in sub-classes). However, to maintain backward compatilibity I can not do either of these.
The library is using extensively pimpl idiom but unfortunately the root class I have to modify does not use this idiom. Sub-classes, however, use this idiom.
Now only solution that I can think of is simulating member variable with static hash-map. So I could create a static hash-map, store this new member to it, and implement static accessors for it. Something like this (in pseudo 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);
...
}
};
Now, while this solution might work, I find it very ugly (of course I could also add non-static accessor functions but this wouldn't remove the ugliness).
Are there any other solutions?
Thank you.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
您可以使用装饰器模式。装饰器可以公开新的数据元素,并且不需要对现有类进行任何更改。如果客户通过工厂获取他们的对象,这种方法效果最好,因为这样你就可以透明地添加装饰器。
You could use the decorator pattern. The decorator could expose the new data-elements, and no change to the existing classes would be needed. This works best if clients obtain their objects through factories, because then you can transparently add the decorators.
最后,使用 abi-compliance-checker 等自动化工具检查二进制兼容性。
Finally, check binary compatibility using automated tools like abi-compliance-checker.
您可以添加导出函数 (declspec import/export),而不影响二进制兼容性(确保您不会删除任何当前函数并在最后添加新函数),但不能通过添加新数据成员来增加类的大小。
您无法增加类大小的原因是,对于使用旧大小编译但使用新扩展类的人来说,这意味着数据成员存储在您的类之后的对象中(如果您添加超过 1 个单词,则数据成员会存储更多)到新课程结束时就会被扔掉。
例如
旧:
新:
客户端可能有:
在内存中,旧框架中的 ClientOfCounter 看起来像这样:
相同的代码(没有重新编译,但使用新版本看起来像这样)
,即它不知道 iCounter 是现在是 8 个字节而不是 4 个字节,因此 iBlah 实际上被 iCounter 的最后 4 个字节丢弃了。
如果您有备用的私有数据成员,则可以添加 Body 类来存储未来的任何数据成员。
You can add exported functions (declspec import/export) without affecting binary compatibility (ensuring you do not remove any current functions and add your new functions at the end), but you cannot increase the size of the class by adding new data members.
The reason you cannot increase the size of the class is that for someone that compiled using the old size but uses the newly extended class would mean that the data member stored after your class in their object (and more if you add more than 1 word) would get trashed by the end of the new class.
e.g.
Old:
New:
A client then may have:
In memory, ClientOfCounter in the old framework will look something like this:
That same code (not recompiled but using your new version would look like this)
i.e. it doesn't know that iCounter is now 8 bytes rather than 4 bytes, so iBlah is actually trashed by the last 4 bytes of iCounter.
If you have a spare private data member, you can add a Body class to store any future data members.
如果您的库是开源的,那么您可以请求将其添加到upstream-tracker。它将自动检查所有库版本的向后兼容性。这样您就可以轻松维护您的 API。
编辑:qt4库的报告位于此处。
If your library is open-source then you can request to add it to the upstream-tracker. It will automatically check all library releases for backward compatibility. So you can easily maintain your API.
EDIT: reports for qt4 library are here.
维护二进制兼容性很困难——仅维护接口兼容性要容易得多。
我认为唯一合理的解决方案是打破对当前库的支持并重新设计它以仅导出类的纯虚拟接口。
It is hard to maintain binary compatibility - it is much easier to maintain only interface compatibility.
I think that the only reasonable solution is to break supporting current library and redesign it to only export pure virtual interfaces for classes.
将数据成员添加到根目录会破坏二进制兼容性(并强制重建,如果这是您关心的问题),但它不会破坏向后兼容性,添加成员也不会函数(虚拟或非虚拟)。添加新的成员函数是显而易见的方法。
Adding data members to the root will break binary compatibility (and force a rebuild, if that is your concern), but it won't break backward compatibility and neither will adding member functions (virtual or not). Adding new member functions is the obvious way to go.