问题所在
我正在围绕一个面向对象的C库编写一个瘦的C++包装器。其想法是自动化内存管理,但到目前为止,它还不是很自动化。基本上,当我使用包装类时,我会得到各种各样的内存访问和不适当的释放问题。
C库的最小示例
假设C库由A和B类组成,每个类都有一些与它们相关联的“方法”:
#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库”
void testA() {
A *a = c_newA("An A");
c_printA(a);
c_freeA(a);
// outputs: "An A"
// valgrind is happy =]
}void testB() {
B *b = c_newB("first A", "second A");
c_printB(b);
c_freeB(b);
// outputs: "first A, second A"
// valgrind is happy =]
}A和B的包装类
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()));
}
};包装试验
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++将自动分别调用两个类的去核器,但是由于底层库的结构(即对空闲的调用),我们会遇到内存问题:
void testUsingAWrapperAndBWrapperTogether() {
BWrapper b = BWrapper::fromString("first A", "second A");
AWrapper a1 = b.getFirstA();
// valgrind no happy =[
}瓦磨输出

我试过的东西
克隆不可能
我尝试的第一件事是获取A的副本,而不是让它们释放相同的A。这虽然是个好主意,但在我的情况下是不可能的,因为我正在使用的库的性质。实际上,这里有一个捕获机制,这样当您创建一个新的A时,它会给您返回相同的A。A。
自定义析构函数
我把C库析构函数(这里是freeA和freeB )的代码复制到源代码中。然后,我试图修改它们,使A不能被B释放。一些内存问题的实例已经解决,但是由于这个想法没有解决手头的问题(只是暂时掩盖了主要问题),新的问题不断出现,其中一些问题很模糊,很难调试。
问题是
最后,我们讨论了一个问题:我如何修改这个C++包装器,以解决由于底层C对象之间的交互而产生的内存问题?我能更好地利用智能指针吗?我应该完全放弃C包装器,而只是按原样使用库指针吗?还是有更好的办法我没想过?
提前谢谢。
编辑:对评论的答复
自从问了前面的问题(链接在上面)之后,我已经对我的代码进行了重组,这样包装器的开发和构建与包装的库相同。所以这些物体不再是不透明的。
指针是通过对库的函数调用生成的,库使用calloc或malloc进行分配。
在实际代码中,A是来自raptor2的raptor_uri* (typdef librdf_uri*),它是用uri分配的,B是raptor_term* (又名librdf_node*),是用函数分配的。librdf_node有一个librdf_uri字段。
编辑2
我还可以指向代码行,如果它是相同的字符串,则返回相同的A。请参阅这里第137行
发布于 2020-05-31 00:06:52
问题是getFirstA和getSecondA返回AWrapper的实例,这是一个拥有类型。这意味着在构建AWrapper时,您将放弃A *的所有权,但是getFirstA和getFirstB不会这样做。构造返回对象的指针由BWrapper管理。
最简单的解决方案是返回一个A *,而不是包装类。这样,您就不会传递内部A成员的所有权。我还建议将接受包装类中的指针的构造函数设置为私有,并具有类似于fromPointer的类似于fromString的静态方法,该方法获取传递给它的指针的所有权。这样,您就不会意外地从原始指针中创建包装类的实例。
如果您希望避免使用原始指针,或者希望对来自getFirstA和getSecondA的返回对象使用方法,则可以编写一个简单的引用包装器,其中有一个原始指针作为成员。
class AReference
{
private:
A *a_ref_;
public:
explicit AReference(A *a_ref) : a_ref_(a_ref) {}
// other methods here, such as print or get
};发布于 2020-05-30 23:35:07
你解放了A两次
BWrapper b = BWrapper::fromString("first A", "second A");当b超出作用域时,c_freeB被调用,它也调用c_freeA
AWrapper a1 = b.getFirstA();用另一个unique_ptr包装A,然后当a1超出范围时,它将在相同的A上调用c_freeA。
注意,在使用getFirstA构造函数时,BWrapper中的unique_ptr将A的所有权赋予了另一个unique_ptr。
解决这个问题的方法:
编辑:
编辑2:
在这个特殊情况下,考虑到您提到的猛禽库,您可以尝试以下方法:
explicit AWrapper(A *a)
: aptr_(raptor_uri_copy(a)) {
}假设A是raptor_uri。raptor_uri_copy(raptor_uri *)将增加引用计数,并返回相同的传递指针。然后,即使raptor_free_uri在同一个raptor_uri *上被调用了两次,它也只有在计数器变为零时才会调用空闲。
https://stackoverflow.com/questions/62109576
复制相似问题