与成员对象共享成员变量以实现 API 重构期间的向后兼容性

Share member variables with member object for API backwards compatibility during refactor

问题

这个问题可能看起来很奇怪。我想让一个成员变量与另一个成员变量的成员变量之一共享相同的地址space。例如,

struct Bar { int a; }
struct Foo {
  Bar bar;
  union {  // make a2 share the same address space as bar.a
    int a2;
    int bar.a;  // invalid syntax
  }
}

int main() {
  Foo foo;
  foo.bar.a = 5;
  cout << foo.a2 << endl;  // 5
}

我希望 foo.a2foo.bar.a 共享同一地址 space。我还将解释我为什么要这样做,因为可能有一个我没有想到的更好的方法。

为什么/背景/动机

我正在重构一个库,需要保持 API 向后兼容性(不是 ABI,只是面向 public 的变量、函数、classes 等 names/syntaxes 需要向后兼容)。有一大块 code/parameters 确实应该移到它自己的 class 中,但我很难弄清楚如何在不破坏兼容性的情况下这样做。例如,考虑这个淡化的片段:

struct NonlinearOptimizerParams {
  // these variables make sense as nonlinear optimization parameters
  int maxIterations, errorTol, method;
  // but these variables should be in a separate parameter struct
  int linearSolverType, linearSolverOrdering, linearSolverTolerance;
};

和客户端代码:

int main() {
  NonlinearOptimizerParams params;
  int &lst1 = params.linearSolverTolerance;
  int *lst2 = &params.linearSolverTolerance;

  params.linearSolverTolerance = 2;
  cout << params.linearSolverTolerance << endl;  // 2
  lst1 = 3;
  cout << params.linearSolverTolerance << endl;  // 3
  *lst2 = 4;
  cout << params.linearSolverTolerance << endl;  // 4
}

我想将 NonlinearOptimizerParams 拆分为 2 个结构(代码绝对应该根据代码库的其余部分以这种方式拆分 - 我礼貌地请求您信任我并且不要反对我的结束目标):

struct LinearOptimizerParams {
    int linearSolverType, linearSolverOrdering, linearSolverTolerance;
};

struct NonlinearOptimizerParams {
    int maxIterations, errorTol, method;
    LinearOptimizerParams loParams;
};

其中 NonlinearOptimizerParams 持有类型 LinearOptimizerParams 的成员变量。但这会破坏向后兼容性,即 params.linearSolverTolerance 需要在客户端代码中更改为 params.loParams.linearSolverTolerance

所以我想在 NonlinearOptimizerParams 中保留不推荐使用的变量,这些变量与 NonlinearOptimizerParams.loParams 中的变量“自动同步”。即我希望 params.linearSolverTolerance 成为 params.loParams.linearSolverTolerance 的“别名”。例如,

struct LinearOptimizerParams {
  int linearSolverType, linearSolverOrdering, linearSolverTolerance;
};

struct NonlinearOptimizerParams {
  int maxIterations, errorTol, method;
  LinearOptimizerParams loParams;
  int linearSolverType = loParams.linearSolverType; // @deprecated
  int linearSolverOrdering = loParams.linearSolverOrdering; // @deprecated
  int linearSolverTolerance = loParams.linearSolverTolerance; // @deprecated
};

除了等号表示“符号链接”之类的东西(也就是共享相同的地址 space)。

这可能吗?

初步尝试

继承

我想到的一种方法是让 NonlinearOptimizerParamsLinearOptimizerParams 继承,这样非线性就可以访问所有线性的成员变量,如下所示:

struct LinearOptimizerParams {
  int linearSolverType, linearSolverOrdering, linearSolverTolerance;
};

struct NonlinearOptimizerParams : public LinearOptimizerParams {
  int maxIterations, errorTol, method;
};

但问题是语义上这并没有多大意义,因为 NonlinearOptimizer 不是 LinearOptimizer 的类型 .同样,NonlinearOptimizerParams 不是 LinearOptimizerParams 的 type。相反,NonlinearOptimizerParams 包含 LinearOptimizerParams。我担心忽略语义会导致可维护性问题。

联盟

我想到的另一种 hacky 可疑的未定义行为方式是这样的:

struct LinearOptimizerParams {
  int linearSolverType, linearSolverOrdering, linearSolverTolerance;
};

struct NonlinearOptimizerParams {
  int maxIterations, errorTol, method;
  union {
      LinearOptimizerParams loParams;
      int linearSolverType, linearSolverOrdering, linearSolverTolerance;
  };
};

但这似乎是一场噩梦,要确保变量的顺序在不同机器上和随着时间的推移保持一致——这不是真正可接受的代码。

作为成员变量的引用

我也尝试过类似的方法,其中 LinearOptimizerParams 使用引用作为成员变量:


struct LinearOptimizerParams {
  int &linearSolverType, &linearSolverOrdering, &linearSolverTolerance;
};

struct NonlinearOptimizerParams : LinearOptimizerParams {
  NonlinearOptimizerParams() {
    loParams.linearSolverType = linearSolverType;
    ...
  }
  int maxIterations, errorTol, method;
  int linearSolverType, linearSolverOrdering, linearSolverTolerance;
  LinearOptimizerParams loParams;
};

但是现在初始化 LinearOptimizerParams 变得困难并且与 API 的其余部分不兼容(我需要一个默认构造函数)。

我知道这是一个有点奇怪的问题,但我希望有人有一些想法!

我认为唯一明智的方法是引用。但你是倒着做的,即这样试试:

struct LinearOptimizerParams {
  int linearSolverType, linearSolverOrdering, linearSolverTolerance;
};

struct NonlinearOptimizerParams : LinearOptimizerParams {
  int maxIterations, errorTol, method;
  LinearOptimizerParams loParams;
  int& linearSolverType {loParams.linearSolverType};
  int& linearSolverOrdering {loParams.linearSolverOrdering};
  int& linearSolverTolerance {loParams.linearSolverTolerance};
};

你甚至可以做宏来定义这些参数,这样你就可以缩短declaration/initialization。

此方法的唯一问题是您需要删除或定义 copy/move 构造函数。因为 default/implicit 实现忽略成员初始化。