Проблема

Я пишу тонкую оболочку 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 lib'

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

enter image description here

Вещи, которые я пробовал

Клонирование невозможно

Первым делом я попытался взять копию A, а не пытаться освободить тот же A. Это, хотя и хорошая идея, в моем случае невозможно из-за характера используемой библиотеки. На самом деле существует механизм захвата, так что когда вы создаете новый A со строкой, которую видели ранее, он вернет вам тот же A. См. Этот вопрос, чтобы узнать о моих попытках клонирования A.

Пользовательские деструкторы

Я взял код для деструкторов библиотеки C (freeA и freeB здесь) и скопировал их в свой исходный код. Затем я попытался изменить их так, чтобы A не освобождался B. Это частично сработало. Некоторые случаи проблем с памятью были решены, но, поскольку эта идея не решает проблему под рукой (просто временное затмение основной проблемы), появляются новые проблемы, некоторые из которых неясны и трудны для отладки.

Вопрос

Итак, наконец, мы задаемся вопросом: как я могу изменить эту оболочку C ++, чтобы решить проблемы с памятью, возникающие из-за взаимодействия между базовыми объектами C? Могу ли я лучше использовать умные указатели? Должен ли я полностью отказаться от оболочки C и просто использовать указатели библиотек как есть? Или есть лучший способ, о котором я не думал?

Заранее спасибо.

Редактирование: ответ на комментарии

После того, как я задал предыдущий вопрос (ссылка выше), я перестроил свой код так, что оболочка разрабатывается и строится в той же библиотеке, что и оболочка. Таким образом, объекты больше не являются непрозрачными.

Указатели генерируются из вызовов функций библиотеки, которая использует calloc или malloc для выделения.

В реальном коде A является raptor_uri* (typdef librdf_uri*) из raptor2 и выделяется с помощью librdf_new_uri, в то время как B равен raptor_term* (иначе librdf_node*) и выделен с помощью < a href = "http://librdf.org/docs/api/redland-node.html#librdf-new-node" rel = "nofollow noreferrer"> librdf_new_node_ * functions . librdf_node имеет поле librdf_uri.

Редактировать 2

Я также могу указать на строку кода, где возвращается тот же A, если это та же строка. См. строку 137 здесь.

0
CiaranWelsh 31 Май 2020 в 01:51

3 ответа

Лучший ответ

Проблема в том, что 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

};
1
IlCapitano 31 Май 2020 в 00:12

Вы освобождаете дважды

BWrapper b = BWrapper::fromString("first A", "second A");

Когда b выходит из области видимости, вызывается c_freeB, который также вызывает c_freeA

AWrapper a1 = b.getFirstA();

Обертывает A другим unique_ptr, затем, когда a1 выходит из области видимости, он вызывает c_freeA для того же A.

Обратите внимание, что getFirstA в BWrapper дает право владения A другому unique_ptr при использовании конструктора AWrapper.

Способы исправить это:

  1. Не позволяйте B управлять памятью, но поскольку вы используете библиотеку, это будет невозможно.
  2. Позвольте BWrapper управлять A, не позволяйте AWrapper управлять A и убедитесь, что BWrapper существует при использовании AWrapper. То есть используйте в AWrapper необработанный указатель вместо умного указателя.
  3. Сделайте копию A в конструкторе AWrapper (A *), для этого вы можете использовать функцию из библиотеки.

Редактировать:

  1. shared_ptr не будет работать в этом случае, потому что c_freeB будет вызывать c_freeA в любом случае.

Изменить 2:

В этом конкретном случае, с учетом упомянутой вами библиотеки raptor, вы можете попробовать следующее:

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

Предполагая, что A является raptor_uri. raptor_uri_copy(raptor_uri *) увеличит количество ссылок и вернет тот же переданный указатель. Тогда, даже если raptor_free_uri вызывается дважды для одного и того же raptor_uri *, он вызовет free только тогда, когда счетчик станет равным нулю.

1
cmt 31 Май 2020 в 01:41

Установите значение NULL после освобождения и проверьте, имеет ли оно значение NULL до освобождения

#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)); 
    auto *s = (char *) malloc(strlen(string) + 1);
    strcpy(s, string);
    a->string = (unsigned char *) s;
    return a;
}

void c_freeA(A **ap) {
    A* a = *ap;
    if (a) {
        free(a->string);
        free(a);
        a = NULL;
    }
}

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 **bp) {
    B *b = *bp;
    if (b) {
        c_freeA(&b->firstA);
        c_freeA(&b->secondA);
        free(b);
        b = NULL;
    }
}

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;
}

}

Классы обертки для 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()));
    }

};
-1
Ôrel 30 Май 2020 в 23:27