C语言 语义版本控制:改变"应该"通过库函数分配的非不透明结构体



我的C库在1.0.0版本中定义了一个结构体和一些函数来分配和使用该结构体:

typedef struct { int x; int y; } MyStruct;
MyStruct *allocate( int, int );
void destroy( MyStruct* );
void print( MyStruct* );

用户不应该自己分配结构体,也不应该按值复制它。这是与语义版本控制问题的主要区别:小变化还是大变化?例如,程序应该这样使用它:

void f(){
MyStruct *ms = allocate(0,0);
ms->x += 1;
print(ms);
destroy(ms);
}

现在我需要在结构体中添加一个新字段,而函数的签名没有改变。

typedef struct { int x; int y; int z; } MyStruct;

新的结构体比旧的结构体占用更多的内存:如果一个程序试图直接分配一个MyStruct实例或按值复制它,如果它链接到一个不同于它构建时使用的库版本,它可能会崩溃。

然而,程序不是这样使用MyStruct的:只要它们遵循文档,一切都可以正常工作。但是代码中没有任何东西可以阻止他们滥用结构体。

我正在使用语义版本控制来版本我的库。在上面的情况下,我应该增加次要版本(以向后兼容的方式添加的功能)还是增加主要版本(不兼容的API更改)?

您对一个公开可见的结构进行了破坏性的更改,在C中,它与早期版本不向后兼容。除了结构大小的变化之外,您自己的示例还准确地说明了为什么这是一个突破性的变化:

void f(){
MyStruct *ms = allocate(0,0);
ms->x += 1;
print(ms);   // Indicates you are aware of external dependencies.
destroy(ms);
}

您正在公开一个数据结构,它可能被您的客户合并到某种类型的数据集中。你可能对如何使用你的图书馆有一些想法,但我可以向你保证,你的客户总是会给你带来惊喜。

考虑到你发布的代码,以及你声明的意图是如何使用你的库,我想说你需要重新设计你的API,这样MyStruct对用户来说是完全不透明的,永远不会改变大小(客户经常缓存这样的东西)。从标准库中借用一个页面,使用句柄。

typedef int MyHandle;
MyHandle allocate(int x, int y);
void destroy(MyHandle h);
void print(MyHandle h);

句柄可以由内部代码进行范围检查,然后用作结构表或结构指针表的索引,或者它可以作为二叉树的键。关键是你可以自由地做任何你喜欢的事情,而不会破坏你的API。

如果您想让x y位可见,请使用可扩展结构体:

typedef struct { 
int x; 
int y; 
void* reserved; // For internal use only!
} MyStruct;

MyStruct。保留字段应该始终为NULL,直到您需要它用于内部使用。请记住,一旦像这样公开数据字段,客户如何使用它们就完全不受您的控制了。当您以这种方式公开结构时,您正在对您的客户做出承诺。

关于getter和setter。

// Using the MyHandle type described earlier:
typedef struct _My_XY {
int x;
int y;
} My_XY;
My_XY GetXY(MyHandle h);

问题解决了。


我将补充说,既然添加z字段无论如何都是一个突破性的更改,并且您似乎觉得需要扩展您的产品的功能,这将是一个彻底重写API的合适时机。但是,如果您必须在不破坏客户基础的情况下扩展它,则可以在内部隐藏z数据点,例如:

// Public API
typedef struct { int x; int y; } MyStruct;
MyStruct *allocate( int, int );
void destroy( MyStruct* );
void print( MyStruct* );
// Implementation
typedef struct _XY_Node {
MyStruct* xy;
int z;
struct _XY_Node *pNext;
} XY_Node;
XY_Node root = null;
XY_Node* AddNode(int x, int y, int z) {...}
XY_Node* RemoveNode(int x, int y, int z) {...}
XY_Node* FindNode(int x, int y, int z) {...}
int DeriveZ(int x, int y) {...}
MyStruct *allocate(int x, int y)
{
return AddNode(x, y, DeriveZ(x, y)).xy;
}
// etc...

你可以使用哈希表,或者某种形式的二叉树,而不是列表。关键是,您不必为了扩展API而破坏API。您可以将其作为1.y的最终版本。z系列,然后彻底检查你的API,这样你就有了一个更干净的客户体验,和一个更有效的实现。

最新更新