将 C++ 接口写入动态分配的 C 结构
Writing a C++ interface to a dynamically allocated C struct
简介:我正在编写一个 C++11 应用程序,它广泛使用了遗留的 C 代码库。遗留代码中一个非常常见的模式是存在一些 struct LegacyStruct
,它们由
等方法构造和销毁
build_struct(LegacyStruct *L, int arg1, int arg2)
free_struct(LegacyStruct *L)
基本上就是constructor/destructor。遗留代码库中的所有权模型非常 unique_ptr
-esque,所以我的目标是将其包装在内存安全的 RAII-minded 包装器 class 中,如下所示:
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
按照 this question 的方式调用 free_struct
,或者只是 std::default_delete
对 LegacyStruct
的部分特化。反正到目前为止一切顺利,我认为这是一种常见的设计模式,很适合我的需要。
我的问题:我在处理形式为
的链表类型结构的情况下无法调整此模式
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);
Edit/clarification: build_list
是代码库中的静态非导出函数,我通过调用其他调用 build_list
可能有好几次。
因此,我会喜欢写一个ListWrap
class,它存储一个头节点和一个列表长度,并且有copy/move与上面 Wrapper
相同的运算符,即列表本身具有唯一所有权,可以移动但不能复制等。
但是,我的理解是智能指针在这种情况下不是一个选项。使用 head_node
作为 LegacyNode 的智能指针,我必须将 &head_node.get()
传递给 build_list
,这会破坏智能指针 invariants/ownership?
就目前而言,我的包装器 class 包含一个指向头节点的原始指针,该方法 returns 头节点的地址供 build_list
使用,一个析构函数它遍历列表调用 free_node
,基于谓词的 erase
类型的方法只删除某些元素。
当然,修改和清除链表是 CS-101 级别的东西,但我仍然设法浪费了几个小时来编写它并且到处都是内存泄漏!此外,遗留代码库中还有其他几个使用几乎相同的链表结构,所以我希望能够将它变成一个 class 模板,它可以专门用于类型和删除器,并继承自提供特定于类型的方法。
谢谢
However, my understanding is that smart pointers are not an option in this case. With head_node
as some smart pointer to a LegacyNode, I would have to pass &head_node.get()
to build_list
, which would corrupt the smart pointer invariants/ownership?
是的,这是正确的,因为 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;
};
这里是一些操作。他们中的大多数人都会管理事情,除了简短的 windows 我评论过:
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
的方法。
简介:我正在编写一个 C++11 应用程序,它广泛使用了遗留的 C 代码库。遗留代码中一个非常常见的模式是存在一些 struct LegacyStruct
,它们由
build_struct(LegacyStruct *L, int arg1, int arg2)
free_struct(LegacyStruct *L)
基本上就是constructor/destructor。遗留代码库中的所有权模型非常 unique_ptr
-esque,所以我的目标是将其包装在内存安全的 RAII-minded 包装器 class 中,如下所示:
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
按照 this question 的方式调用 free_struct
,或者只是 std::default_delete
对 LegacyStruct
的部分特化。反正到目前为止一切顺利,我认为这是一种常见的设计模式,很适合我的需要。
我的问题:我在处理形式为
的链表类型结构的情况下无法调整此模式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);
Edit/clarification: build_list
是代码库中的静态非导出函数,我通过调用其他调用 build_list
可能有好几次。
因此,我会喜欢写一个ListWrap
class,它存储一个头节点和一个列表长度,并且有copy/move与上面 Wrapper
相同的运算符,即列表本身具有唯一所有权,可以移动但不能复制等。
但是,我的理解是智能指针在这种情况下不是一个选项。使用 head_node
作为 LegacyNode 的智能指针,我必须将 &head_node.get()
传递给 build_list
,这会破坏智能指针 invariants/ownership?
就目前而言,我的包装器 class 包含一个指向头节点的原始指针,该方法 returns 头节点的地址供 build_list
使用,一个析构函数它遍历列表调用 free_node
,基于谓词的 erase
类型的方法只删除某些元素。
当然,修改和清除链表是 CS-101 级别的东西,但我仍然设法浪费了几个小时来编写它并且到处都是内存泄漏!此外,遗留代码库中还有其他几个使用几乎相同的链表结构,所以我希望能够将它变成一个 class 模板,它可以专门用于类型和删除器,并继承自提供特定于类型的方法。
谢谢
However, my understanding is that smart pointers are not an option in this case. With
head_node
as some smart pointer to a LegacyNode, I would have to pass&head_node.get()
tobuild_list
, which would corrupt the smart pointer invariants/ownership?
是的,这是正确的,因为 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;
};
这里是一些操作。他们中的大多数人都会管理事情,除了简短的 windows 我评论过:
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
的方法。