我正在围绕面向对象的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;
}
}
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 ++会自动分别调用这两个类的描述符,但是由于基础库的结构(即对free的调用),我们遇到了内存问题:
void testUsingAWrapperAndBWrapperTogether() { BWrapper b = BWrapper::fromString("first A", "second A"); AWrapper a1 = b.getFirstA(); // valgrind no happy =[ }
Valgrind输出
我尝试的第一件事是获取A
的副本,而不是让他们尝试释放相同的A
。对于我来说,这是一个好主意,但由于我所使用的库的性质而无法实现。实际上,这里有一个捕获机制,因此当您创建一个新的A
并使用之前看到过的字符串时,它将带给您相同的A
。 See this question for my attempts at cloning A
。
我获取了C库析构函数的代码(此处为A
和freeA
,并将其复制到我的源代码中。然后,我尝试修改它们,以使A不会被B释放。这部分起作用。一些内存问题的实例已经解决,但是由于这种想法无法解决当前的问题(只是暂时掩盖了主要问题),因此不断出现新的问题,其中有些晦涩难懂且难以调试。
因此,最后我们提出了一个问题:如何修改C ++包装器以解决由于底层C对象之间的交互而引起的内存问题?我可以更好地利用智能指针吗?我是否应该完全放弃C包装程序,而按原样使用库指针?还是有我没想到的更好的方法?
谢谢。
由于问了上一个问题(上面已链接),我重构了代码,以便在与包装的库相同的库中开发和构建包装。因此,对象不再是不透明的。
指针是从对库的函数调用生成的,该库使用freeB
或calloc
进行分配。
在实码中,malloc
是来自A
的raptor_uri*
(typdef librdf_uri*
),并分配有raptor2
,而librdf_new_uri是B
(又名raptor_term*
)并分配有librdf_node*
。 librdf_new_node_* functions有一个librdf_node
字段。
我也可以指向代码行,如果相同的字符串,则返回相同的librdf_uri
。参见A
我正在围绕面向对象的C库编写一个薄C ++包装器的问题。这个想法是要自动化内存管理,但是到目前为止,它并不是很自动。基本上,当我使用包装器时...
问题是line 137 here和getFirstA
返回实例类型getSecondA
的实例。这意味着在构造AWrapper
时,您将放弃AWrapper
的所有权,但是A *
和getFirstA
不会这样做。构造返回的对象的指针由getFirstB
管理。
最简单的解决方案是,您应该返回BWrapper
而不是包装器类。这样,您就不会传递内部A *
成员的所有权。我还建议使构造函数将包装器类中的指针设为私有,并使用类似于A
的fromPointer
静态方法,该方法获取传递给它的指针的所有权。这样,您就不会意外地使用原始指针创建包装类的实例。
您两次释放A
释放后设置为raptor_uri *
,释放前测试它是否为NULL
NULL