将C++接口写入动态分配的 C 结构

Writing a C++ interface to a dynamically allocated C struct

本文关键字:结构 动态分配 C++ 接口      更新时间:2023-10-16

简介:我正在编写一个C++11应用程序,该应用程序广泛使用了遗留的C代码库。遗留代码中一个非常常见的模式是存在一些struct LegacyStruct,这些是通过

诸如
build_struct(LegacyStruct *L, int arg1, int arg2)
free_struct(LegacyStruct *L)

基本上是构造函数/析构函数。遗留代码库中的所有权模型非常unique_ptr式的,因此我的目标是将其包装在内存安全的RAII包装器类中,如下所示:

class Wrapper {
public:
Wrapper::Wraper() : handle() {}
Wrapper::Wrapper(int same_arg1, int same_arg2);
Wrapper::Wrapper(const Wrapper &W) = delete;
Wrapper::Wrapper(Wrapper &&W) : handle(std::move(W.handle)) {}
//copy operator= and move operator= analogously
private:
std::unique_ptr<LegacyStruct, custom_deleter> handle;

custom_deleter按照这个问题的思路调用free_struct,或者只是LegacyStructstd::default_delete的部分专业化.无论如何,到目前为止一切顺利,我认为这是一种常见的设计模式,非常适合我的需求。

的问题:我无法使这种模式适应我正在处理表单的链表类型结构的情况

typedef struct LegacyNode {
int stack_allocated_data;
OtherStruct *heap_allocated_data;
LegacyNode *next;
} LegacyNode;

同样,遗留代码库中的所有权模型是unique_ptr式的:链表拥有唯一的所有权,即负责适当地释放它。类似地,有一个相应的free_node(LegacyNode *N)函数,如有必要,可以释放heap_allocated_data,然后释放节点本身。

不过,施工情况却大不相同。将有一个看起来像

build_list(LegacyNode **L, int *count_p, int other_args){
LegacyNode *newnode;
//code allocating newnode and populating its fields
//...and then:
newcut->next = *L;
*L = newcut;
(*count_p)++;
}

build_list打电话看起来像

int list_count = 0;
LegacyNode *L = (LegacyNode *) NULL;
build_list(&L, &list_count, 99);

编辑/澄清:build_list是代码库中的一个静态、非导出函数,我通过调用其他一些可能调用build_list多次的函数来访问它。

因此,我想编写一个ListWrap类,它存储一个头节点和一个列表长度,并具有与上述Wrapper相同的复制/移动运算符,即对列表本身拥有唯一的所有权,它可以移动但不能复制等。

但是,我的理解是,在这种情况下,智能指针不是一种选择。head_node作为指向 LegacyNode 的智能指针,我必须将&head_node.get()传递给build_list,这会破坏智能指针不变量/所有权?

就目前而言,我的包装类包含一个指向头节点的原始指针,一个返回供build_list使用的头节点地址的方法,一个遍历调用free_node列表的析构函数,以及基于谓词的erase-type方法,该方法只删除某些元素。

当然,修改和清除链表是 CS-101 级别的东西,但我仍然设法浪费了几个小时来编写它并且到处都有内存泄漏!此外,遗留代码库中还有其他几种链表结构,其用法几乎相同,因此我希望能够将其转换为类模板,该模板可以专门用于类型和删除器,并从中继承以提供特定于类型的方法。

谢谢

但是,我的理解是,在这种情况下,智能指针不是一种选择。将head_node作为指向 LegacyNode 的智能指针,我必须将&head_node.get()传递给build_list,这会损坏智能指针不变量/所有权?

是的,这是正确的,因为build_list会覆盖该内存位置的对象,从而损坏智能指针的内存。但是还有另一种方法,您可以使用现有的指针构造std::unique_ptr

因此,您不必ListWrap分配自己的对象,而是build_list分配对象,然后只需获取指针并用 RAII 将它们包装起来。

class ListWrap {
public:
ListWrap(LegacyNode* head, int count);
//...
private:
std::unique_ptr<LegacyNode, &free_node> handle;
int count;
};
ListWrap::ListWrap(LegacyNode* head, int count) : handle{ head }, count{ count } {}

这里有一堆节点:

struct Nodes {
struct DeleteAllNodes {
void operator()(LegacyNode* node)const {
while (auto cur = node) {
node = cur->next;
free_node(node);
}
}
};
std::unique_ptr<LegacyNode, DeleteAllNodes> m_nodes;
};

这里有一些操作。 他们中的大多数人都保持事情的管理,除了我评论过的短窗口:

void push_node( Nodes& nodes, int other_args ) {
int unused = 0;
auto* tmp = nodes.m_nodes.get();
build_list( &tmp, &unused, other_args );
nodes.m_nodes.release(); // unmanaged
nodes.m_nodes.reset(tmp); // everything managed now
}
Nodes pop_node( Nodes& nodes ) {
if (!nodes.m_nodes) return {};
auto* tmp = nodes.m_nodes->next; // unmanaged
nodes.m_nodes->next = nullptr;
Nodes retval;
retval.m_nodes.reset(tmp); // everything managed now
std::swap( retval.m_nodes, nodes.m_nodes );
return retval;
}
void move_single_node( Nodes& dest, Nodes& src ) {
Assert(src.m_nodes);
if (!src.m_nodes) return;
Nodes to_push = pop_node(src);
LegacyNode** next = &(to_push.m_nodes->next);
Assert(!*next); // shouldn't be possible, pop_node returns a single node
*next = dest.m_nodes.release(); // unmanaged for a short period
dest = std::move(to_push);
}
Nodes splice( Nodes backwards, Nodes forwards ) {
while(backwards.m_nodes) {
move_single_node( forwards, backwards );
}
return forwards;
}
template<class F>
void erase_if( Nodes& nodes, F&& f, Nodes prefix={} ) {
if (!nodes.m_nodes) {
return splice( std::move(prefix), std::move(nodes) );
}
Nodes tmp = pop_node( nodes );
if ( !f(*tmp.m_nodes) ) {
prefix = splice( std::move(tmp), prefix );
}
erase_if( nodes, std::forward<F>(f), std::move(prefix) );
}

Nodes&作为第一个参数的可以是Nodes的方法。