语义版本控制:更改*应该*通过库函数分配的非不透明结构
Semantic versioning: changing a non-opaque struct that *should* be allocated through library functions
我的 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
就完全不透明了用户并且永远不会改变大小(客户经常缓存这样的东西)。借用标准库的一页,用个handle.
typedef int MyHandle;
MyHandle allocate(int x, int y);
void destroy(MyHandle h);
void print(MyHandle h);
句柄可以由您的内部代码进行范围检查,然后用作 table 结构或结构指针的索引,或者它可以是二叉树的键。关键是你可以自由地做任何你想做的事,而不会打扰你的 API.
如果您想让 x y
位可见,请使用可扩展结构:
typedef struct {
int x;
int y;
void* reserved; // For internal use only!
} MyStruct;
MyStruct.reserved 字段应始终为 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...
您可以使用散列 table 或某种形式的二叉树,而不是列表。关键是,您不必为了扩展它而破坏 API。您可以将此作为 1.y.z 系列的最终版本,然后彻底检查您的 API,以便您拥有更清晰的客户体验和更高效的实施。
我的 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
就完全不透明了用户并且永远不会改变大小(客户经常缓存这样的东西)。借用标准库的一页,用个handle.
typedef int MyHandle;
MyHandle allocate(int x, int y);
void destroy(MyHandle h);
void print(MyHandle h);
句柄可以由您的内部代码进行范围检查,然后用作 table 结构或结构指针的索引,或者它可以是二叉树的键。关键是你可以自由地做任何你想做的事,而不会打扰你的 API.
如果您想让 x y
位可见,请使用可扩展结构:
typedef struct {
int x;
int y;
void* reserved; // For internal use only!
} MyStruct;
MyStruct.reserved 字段应始终为 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...
您可以使用散列 table 或某种形式的二叉树,而不是列表。关键是,您不必为了扩展它而破坏 API。您可以将此作为 1.y.z 系列的最终版本,然后彻底检查您的 API,以便您拥有更清晰的客户体验和更高效的实施。