首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何解决C++包装器中C对象之间的交互所产生的与内存相关的错误?

如何解决C++包装器中C对象之间的交互所产生的与内存相关的错误?
EN

Stack Overflow用户
提问于 2020-05-30 22:51:14
回答 2查看 90关注 0票数 0

问题所在

我正在围绕一个面向对象的C库编写一个瘦的C++包装器。其想法是自动化内存管理,但到目前为止,它还不是很自动化。基本上,当我使用包装类时,我会得到各种各样的内存访问和不适当的释放问题。

C库的最小示例

假设C库由AB类组成,每个类都有一些与它们相关联的“方法”:

代码语言:javascript
复制
#include <memory>
#include "cstring"
#include "iostream"

extern "C" {
typedef struct {
    unsigned char *string;
} A;

A *c_newA(const char *string) { 
    A *a = (A *) malloc(sizeof(A)); // yes I know, don't use malloc in C++. This is a demo to simulate the C library that uses it. 
    auto *s = (char *) malloc(strlen(string) + 1);
    strcpy(s, string);
    a->string = (unsigned char *) s;
    return a;
}

void c_freeA(A *a) {
    free(a->string);
    free(a);
}

void c_printA(A *a) {
    std::cout << a->string << std::endl;
}


typedef struct {
    A *firstA;
    A *secondA;
} B;

B *c_newB(const char *first, const char *second) {
    B *b = (B *) malloc(sizeof(B));
    b->firstA = c_newA(first);
    b->secondA = c_newA(second);
    return b;
}

void c_freeB(B *b) {
    c_freeA(b->firstA);
    c_freeA(b->secondA);
    free(b);
}

void c_printB(B *b) {
    std::cout << b->firstA->string << ", " << b->secondA->string << std::endl;
}

A *c_getFirstA(B *b) {
    return b->firstA;
}

A *c_getSecondA(B *b) {
    return b->secondA;
}

}

测试“C库”

代码语言:javascript
复制
void testA() {
    A *a = c_newA("An A");
    c_printA(a);
    c_freeA(a);
    // outputs: "An A"
    // valgrind is happy =]
}
代码语言:javascript
复制
void testB() {
    B *b = c_newB("first A", "second A");
    c_printB(b);
    c_freeB(b);
    // outputs: "first A, second A"
    // valgrind is happy =]
}

AB的包装类

代码语言:javascript
复制
class AWrapper {

    struct deleter {
        void operator()(A *a) {
            c_freeA(a);
        }
    };

    std::unique_ptr<A, deleter> aptr_;
public:

    explicit AWrapper(A *a)
            : aptr_(a) {
    }

    static AWrapper fromString(const std::string &string) { // preferred way of instantiating
        A *a = c_newA(string.c_str());
        return AWrapper(a);
    }

    void printA() {
        c_printA(aptr_.get());
    }
};


class BWrapper {

    struct deleter {
        void operator()(B *b) {
            c_freeB(b);
        }
    };

    std::unique_ptr<B, deleter> bptr_;
public:
    explicit BWrapper(B *b)
            : bptr_(std::unique_ptr<B, deleter>(b)) {
    }

    static BWrapper fromString(const std::string &first, const std::string &second) {
        B *b = c_newB(first.c_str(), second.c_str());
        return BWrapper(b);
    }

    void printB() {
        c_printB(bptr_.get());
    }

    AWrapper getFirstA(){
        return AWrapper(c_getFirstA(bptr_.get()));
    }

    AWrapper getSecondA(){
        return AWrapper(c_getSecondA(bptr_.get()));
    }

};

包装试验

代码语言:javascript
复制
void testAWrapper() {
    AWrapper a = AWrapper::fromString("An A");
    a.printA();
    // outputs "An A"
    // valgrind is happy =]
}

void testBWrapper() {
    BWrapper b = BWrapper::fromString("first A", "second A");
    b.printB();
    // outputs "first A"
    // valgrind is happy =]
}

问题的论证

很好,所以我继续并开发了完整的包装器(很多类),并意识到当这样的类(即聚合关系)都在作用域中时,C++将自动分别调用两个类的去核器,但是由于底层库的结构(即对空闲的调用),我们会遇到内存问题:

代码语言:javascript
复制
void testUsingAWrapperAndBWrapperTogether() {
    BWrapper b = BWrapper::fromString("first A", "second A");
    AWrapper a1 = b.getFirstA();
    // valgrind no happy =[

}

瓦磨输出

我试过的东西

克隆不可能

我尝试的第一件事是获取A的副本,而不是让它们释放相同的A。这虽然是个好主意,但在我的情况下是不可能的,因为我正在使用的库的性质。实际上,这里有一个捕获机制,这样当您创建一个新的A时,它会给您返回相同的AA

自定义析构函数

我把C库析构函数(这里是freeAfreeB )的代码复制到源代码中。然后,我试图修改它们,使A不能被B释放。一些内存问题的实例已经解决,但是由于这个想法没有解决手头的问题(只是暂时掩盖了主要问题),新的问题不断出现,其中一些问题很模糊,很难调试。

问题是

最后,我们讨论了一个问题:我如何修改这个C++包装器,以解决由于底层C对象之间的交互而产生的内存问题?我能更好地利用智能指针吗?我应该完全放弃C包装器,而只是按原样使用库指针吗?还是有更好的办法我没想过?

提前谢谢。

编辑:对评论的答复

自从问了前面的问题(链接在上面)之后,我已经对我的代码进行了重组,这样包装器的开发和构建与包装的库相同。所以这些物体不再是不透明的。

指针是通过对库的函数调用生成的,库使用callocmalloc进行分配。

在实际代码中,A是来自raptor2raptor_uri* (typdef librdf_uri*),它是用uri分配的,Braptor_term* (又名librdf_node*),是用函数分配的。librdf_node有一个librdf_uri字段。

编辑2

我还可以指向代码行,如果它是相同的字符串,则返回相同的A。请参阅这里第137行

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-05-31 00:06:52

问题是getFirstAgetSecondA返回AWrapper的实例,这是一个拥有类型。这意味着在构建AWrapper时,您将放弃A *的所有权,但是getFirstAgetFirstB不会这样做。构造返回对象的指针由BWrapper管理。

最简单的解决方案是返回一个A *,而不是包装类。这样,您就不会传递内部A成员的所有权。我还建议将接受包装类中的指针的构造函数设置为私有,并具有类似于fromPointer的类似于fromString的静态方法,该方法获取传递给它的指针的所有权。这样,您就不会意外地从原始指针中创建包装类的实例。

如果您希望避免使用原始指针,或者希望对来自getFirstAgetSecondA的返回对象使用方法,则可以编写一个简单的引用包装器,其中有一个原始指针作为成员。

代码语言:javascript
复制
class AReference
{
private:
    A *a_ref_;
public:
    explicit AReference(A *a_ref) : a_ref_(a_ref) {}

    // other methods here, such as print or get

};
票数 1
EN

Stack Overflow用户

发布于 2020-05-30 23:35:07

你解放了A两次

代码语言:javascript
复制
BWrapper b = BWrapper::fromString("first A", "second A");

当b超出作用域时,c_freeB被调用,它也调用c_freeA

代码语言:javascript
复制
AWrapper a1 = b.getFirstA();

用另一个unique_ptr包装A,然后当a1超出范围时,它将在相同的A上调用c_freeA

注意,在使用getFirstA构造函数时,BWrapper中的unique_ptr将A的所有权赋予了另一个unique_ptr。

解决这个问题的方法:

  1. 不要让B管理A内存,但是由于您使用的是lib,所以这是不可能的。
  2. 让BWrapper管理A,不要让AWrapper管理A,并确保BWrapper在使用AWrapper时存在。也就是说,在AWrapper中使用原始指针而不是智能指针。
  3. 在AWrapper(A *)构造函数中复制A,为此您可能需要使用库中的函数。

编辑:

  1. 在这种情况下,shared_ptr无法工作,因为c_freeB无论如何都会调用c_freeA。

编辑2:

在这个特殊情况下,考虑到您提到的猛禽库,您可以尝试以下方法:

代码语言:javascript
复制
explicit AWrapper(A *a)
            : aptr_(raptor_uri_copy(a)) {
}

假设A是raptor_uriraptor_uri_copy(raptor_uri *)将增加引用计数,并返回相同的传递指针。然后,即使raptor_free_uri在同一个raptor_uri *上被调用了两次,它也只有在计数器变为零时才会调用空闲。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62109576

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档