拆分维护类接口的长方法
在我的库中有一个这样的类:
class Foo {
public:
void doSomething();
};
现在, doSomething()
的实现已经增长了很多,我想将其分为两种方法:
class Foo {
public:
void doSomething();
private:
void doSomething1();
void doSomething2();
};
其中 doSomething()
实现是这样的:
void Foo::doSomething() {
this->doSomething1();
this->doSomething2();
}
但是现在类接口已经改变了。如果我编译这个库,所有使用这个库的现有应用程序都将无法工作,外部链接会被更改。
如何避免破坏二进制兼容性?
我猜内联解决了这个问题。对吗?它是便携式的吗?如果编译器优化取消内联这些方法会发生什么?
class Foo {
public:
void doSomething();
private:
inline void doSomething1();
inline void doSomething2();
};
void Foo::doSomething1() {
/* some code here */
}
void Foo::doSomething2() {
/* some code here */
}
void Foo::doSomething() {
this->doSomething1();
this->doSomething2();
}
编辑: 我在方法分割之前和之后测试了这段代码,它似乎保持了二进制兼容性。但我不确定这是否适用于每个操作系统和每个编译器以及更复杂的类(使用虚拟方法、继承......)。有时,在添加此类私有方法后,我会遇到二进制兼容性问题,但现在我不记得在哪种特定情况下。也许这是由于按索引查找符号表(就像 Steve Jessop 在他的答案中指出的那样)。
In my library there's a class like this:
class Foo {
public:
void doSomething();
};
Now, implementation of doSomething()
has been grow a lot and I want to split it in two methods:
class Foo {
public:
void doSomething();
private:
void doSomething1();
void doSomething2();
};
Where doSomething()
implementation is this:
void Foo::doSomething() {
this->doSomething1();
this->doSomething2();
}
But now class interface has changed. If I compile this library, all existent applications using this library wont work, external linkage is changed.
How can I avoid breaking of binary compatibility?
I guess inlining solves this problem. Is it right? And is it portable? What happen if compiler optimization uninlines these methods?
class Foo {
public:
void doSomething();
private:
inline void doSomething1();
inline void doSomething2();
};
void Foo::doSomething1() {
/* some code here */
}
void Foo::doSomething2() {
/* some code here */
}
void Foo::doSomething() {
this->doSomething1();
this->doSomething2();
}
EDIT:
I tested this code before and after method splitting and it seems to maintain binary compatibility. But I'm not sure this would work in every OS and every compiler and with more complex classes (with virtual methods, inheritance...). Sometimes I had binary compatibility breaking after adding private methods like these, but now I don't remember in which particular situation. Maybe it was due to symbol tabled looked by index (like Steve Jessop notes in his answer).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
严格来说,更改类定义(以您显示的任何一种方式)都违反了单一定义规则并导致未定义的行为。
实际上,向类添加非虚拟成员函数可以在每个实现中保持二进制兼容性,因为如果不这样做,您将失去动态库的大部分好处。但是 C++ 标准没有对动态库或二进制兼容性进行太多说明(什么?),因此它不能保证您可以进行哪些更改。
因此,在实践中,只要动态链接器按名称查找符号表中的条目,更改符号表并不重要。符号表中的条目比以前更多,但这没关系,因为所有旧条目仍然具有相同的损坏名称。在您的实现中,私有和/或内联函数(或您指定的任何函数)可能不会导出 dll,但您不需要依赖它。
我使用过一种系统(Symbian),其中符号表中的条目不是按名称查找的,而是按索引查找的。在该系统上,当您向动态库添加任何内容时,您必须确保将任何新函数添加到符号表的末尾,这是通过在特殊的配置文件中列出所需的顺序来完成的。您可以确保二进制兼容性不会被破坏,但这相当乏味。
因此,您可以检查您的 C++ ABI 或编译器/链接器文档以确保绝对确定,或者只相信我的话并继续。
Strictly speaking, changing the class definition at all (in either of the ways you show) is a violation of the One Definition Rule and leads to undefined behavior.
In practice, adding non-virtual member functions to a class maintains binary compatibility in every implementation out there, because if it didn't then you'd lose most of the benefits of dynamic libraries. But the C++ standard doesn't say much (anything?) about dynamic libraries or binary compatibility, so it doesn't guarantee what changes you can make.
So in practice, changing the symbol table doesn't matter provided that the dynamic linker looks up entries in the symbol table by name. There are more entries in the symbol table than before, but that's OK because all the old ones still have the same mangled names. It may be that with your implementation, private and/or inline functions (or any functions you specify) aren't dll-exported, but you don't need to rely on that.
I have used one system (Symbian) where entries in the symbol table were not looked up by name, they were looked up by index. On that system, when you added anything to a dynamic library you had to ensure that any new functions were added to the end of the symbol table, which you did by listing the required order in a special config file. You could ensure that binary compatibility wasn't broken, but it was fairly tedious.
So, you could check your C++ ABI or compiler/linker documentation to be absolutely sure, or just take my word for it and go ahead.
这里没有问题。无论其实现如何,
Foo::doSomething()
的名称修饰始终相同。There is no problem here. The name mangling of
Foo::doSomething()
is always the same regardless of it's implementation.我认为如果添加非虚拟方法,类的 ABI 不会改变,因为非虚拟方法不存储在类对象中,而是作为具有损坏名称的函数存储。只要不添加类成员,您可以添加任意数量的函数。
I think the ABI of the class won't change if you add non-virtual methods because non-virtual methods are not stored in the class object, but rather as functions with mangled names. You can add as many functions as you like as long as you don't add class members.