【问题标题】:Splitting long method maintaining class interface拆分长方法维护类接口
【发布时间】:2011-12-07 10:31:48
【问题描述】:

在我的图书馆里有这样一个类:

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 在他的回答中的注释)。

【问题讨论】:

  • 为什么类的私有部分的变化会影响外部链接?
  • 您可以从一开始就使用 Pimpl 来防止这个问题 - 它封装了这些更改。
  • @littleadv:您可以对破坏二进制兼容性的类的私有部分进行很多更改。添加私有虚函数,添加私有数据成员。这恰好不是其中之一,至少在我知道的实现中,但函数是私有的事实与它无关。

标签: c++ interface methods inline


【解决方案1】:

严格来说,完全更改类定义(以您显示的任何一种方式)都违反了单一定义规则并导致未定义的行为。

在实践中,向类添加非虚拟成员函数可以在每个实现中保持二进制兼容性,因为如果不这样做,那么您将失去动态库的大部分好处。但是 C++ 标准并没有对动态库或二进制兼容性进行太多说明(什么?),因此它不能保证您可以进行哪些更改。

所以在实践中,只要动态链接器按名称查找符号表中的条目,更改符号表并不重要。符号表中的条目比以前更多,但这没关系,因为所有旧的仍然具有相同的重命名。在您的实现中,私有和/或内联函数(或您指定的任何函数)可能不是 dll 导出的,但您不需要依赖它。

我使用了一个系统(Symbian),其中符号表中的条目不是按名称查找的,而是按索引查找的。在该系统上,当您向动态库添加任何内容时,您必须确保将任何新函数添加到符号表的末尾,您可以通过在特殊的配置文件中列出所需的顺序来做到这一点。您可以确保二进制兼容性没有被破坏,但它相当乏味。

因此,您可以检查您的 C++ ABI 或编译器/链接器文档以绝对确定,或者相信我的话并继续。

【讨论】:

  • 所以......我们不确定在不知道编译器/链接器行为的情况下不会破坏二进制兼容性。但是内联添加的函数总是二进制兼容安全的?
  • @Alessandro:在库中,函数是否实际内联到特定调用站点应该没有区别。作为程序员,你无法知道给定的调用是否会被内联,所以如果一个实现破坏了基于它是否实际发生的兼容性,那将是非常苛刻的。也没有充分的理由让它有所作为 - 当程序跳转到 dll 中的代码时,该 dll 代码是否在返回之前跳转到 dll 中的其他位置,或者只是运行一些内联​​代码并不重要代码并返回。
  • 调用者和dll之间的东西略有不同。如果某个函数被内联到调用可执行文件中,然后您更改了同一个内联函数的 dll 中的定义,那么您再次破坏了 ODR,但这一次可能很重要——最好的情况是调用代码继续执行旧定义。就我个人而言,如果不仔细研究它,我不会指望它,我什至只会费心检查是否绝对必要,因为我被一些早期的设计决定画到了一个角落。
【解决方案2】:

这里没有问题。 Foo::doSomething() 的名称修饰始终相同,无论其实现如何。

【讨论】:

    【解决方案3】:

    我认为如果添加非虚拟方法,类的 ABI 不会改变,因为非虚拟方法不存储在类对象中,而是作为名称错位的函数。只要不添加类成员,就可以添加任意数量的函数。

    【讨论】:

      猜你喜欢
      • 2020-12-27
      • 1970-01-01
      • 2013-02-26
      • 2014-10-13
      • 2010-11-26
      • 1970-01-01
      • 1970-01-01
      • 2013-04-07
      • 2013-09-02
      相关资源
      最近更新 更多