C++ 编写C++;与动态分配的C结构的接口

C++ 编写C++;与动态分配的C结构的接口,c++,c++11,design-patterns,raii,C++,C++11,Design Patterns,Raii,简介:我正在编写一个C++11应用程序,它广泛使用了遗留C代码库。遗留代码中的一个非常常见的模式是存在一些struct LegacyStruct,它们是通过以下方法构造和销毁的 build_struct(LegacyStruct *L, int arg1, int arg2) free_struct(LegacyStruct *L) 基本上是构造函数/析构函数。遗留代码库中的所有权模型是非常独特的,因此我的目标是将其封装在内存安全、具有RAII思想的包装类中,如下所示: class Wrapp

简介:我正在编写一个C++11应用程序,它广泛使用了遗留C代码库。遗留代码中的一个非常常见的模式是存在一些
struct LegacyStruct
,它们是通过以下方法构造和销毁的

build_struct(LegacyStruct *L, int arg1, int arg2)
free_struct(LegacyStruct *L)
基本上是构造函数/析构函数。遗留代码库中的所有权模型是非常独特的,因此我的目标是将其封装在内存安全、具有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;
同样,遗留代码库中的所有权模型是独特的:链表是唯一的所有权,即适当释放链表的责任。类似地,有一个相应的
free_节点(LegacyNode*N)
函数,它在必要时释放
heap_分配的_数据,然后释放节点本身

但建筑情况却大不相同。将有一个函数

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)++;
}
int list_count = 0;
LegacyNode *L = (LegacyNode *) NULL;

build_list(&L, &list_count, 99);
调用
build\u list
如下所示

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)++;
}
int list_count = 0;
LegacyNode *L = (LegacyNode *) NULL;

build_list(&L, &list_count, 99);
编辑/澄清:
build\u list
是代码库中的一个静态、非导出函数,我可以通过调用调用
build\u list
的其他函数来访问它

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

然而,我的理解是,在这种情况下,智能指针不是一个选项。使用
head\u node
作为指向LegacyNode的智能指针,我必须将
head\u node.get()
传递到
build\u list
,这会损坏智能指针不变量/所有权吗

目前,我的包装器类包含一个指向头节点的原始指针、一个返回头节点地址供
build\u list
使用的方法、一个遍历调用
free\u节点的列表的析构函数,以及一个只删除某些元素的基于谓词的
erase
类型方法

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

谢谢

然而,我的理解是,在这种情况下,智能指针不是一个选项。使用
head\u node
作为指向LegacyNode的智能指针,我必须将
head\u node.get()
传递到
build\u list
,这会损坏智能指针不变量/所有权吗

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

因此,您没有分配自己的对象,而是分配对象,然后使用指针和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 } {}
类ListWrap{ 公众: ListWrap(LegacyNode*头,整数计数); //... 私人: std::唯一的ptr手柄; 整数计数; }; ListWrap::ListWrap(LegacyNode*head,int count):句柄{head},计数{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;
};
struct节点{
结构DeleteAllNodes{
void运算符()(LegacyNode*节点)常量{
while(自动电流=节点){
node=cur->next;
自由_节点(node);
}
}
};
std::唯一的\u ptr m_节点;
};
下面是一些操作。除了我评论过的短窗口外,它们中的大多数都保持着对事物的管理:

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) );
}
void push_节点(节点和节点,int-other_参数){
int=0;
auto*tmp=nodes.m_nodes.get();
生成参数列表(&tmp,&unused,其他参数);
nodes.m_nodes.release();//非托管
nodes.m_nodes.reset(tmp);//现在管理所有内容
}
节点pop_节点(节点和节点){
如果(!nodes.m_nodes)返回{};
auto*tmp=nodes.m_nodes->next;//非托管
nodes.m_nodes->next=nullptr;
节点返回;
retval.m_nodes.reset(tmp);//现在管理所有内容
std::swap(retval.m_节点,nodes.m_节点);
返回返回;
}
无效移动\单个\节点(节点和目的地、节点和src){
断言(src.m_节点);
如果(!src.m_节点)返回;
推送节点=弹出节点(src);
LegacyNode**next=&(to_push.m_nodes->next);
Assert(!*next);//不应该是可能的,pop_节点返回单个节点
*next=dest.m_nodes.release();//短期内处于非托管状态
dest=std::移动(到推);
}
节点拼接(节点向后,节点向前){
while(向后.m_节点){
移动单个节点(向前、向后);
}
远期收益;
}
模板
void erase_if(节点和节点,F和F,节点前缀={}){
如果(!nodes.m_nodes){
返回拼接(std::move(前缀),std::move(节点));
}
节点tmp=pop_节点(节点);
if(!f(*tmp.m_节点)){
前缀=拼接(标准::移动(tmp),前缀);
}
擦除_if(节点,std::forward(f),std::move(前缀));
}

<> >以<代码>节点和<代码>为第一个参数的方法可以是<代码>节点 .< /P> <代码>和头节点.GETE()>代码>不是合法C++,你会发现.@ yAK非常正确,我可以说这是我可能天真地想做的,但是这是个可怕的想法,而且是不允许的!非常感谢。由于某种原因,我没有想到在构建列表之后获得它的所有权,但是这更适合我的用例。谢谢你的回答。我已编辑了我的问题,以澄清我无权从ou访问
build\u list