RAII - Указатели класса и область применения - PullRequest
2 голосов
/ 13 марта 2012

Я хочу лучше понять, как реализовать идиому RAII с моими классами, на примере: каков рекомендуемый метод для обеспечения правильности указателей free () в моем классе?

У меня есть класс, который должен существовать на протяжении всей программы. В духе RAII и потому, что мне нужно передать ссылку на этот класс другим классам, я держу его в shared_ptr (не уверен, что его действительно нужно хранить в shared_ptr, но для интереса, это так).

В классе ctor я использую 2 буфера (указателя) и затем многократно повторяю цикл malloc (), используя буфер и затем освобождая (). Dtor должен содержать отказоустойчивый код для освобождения буферов в случае сбоя.

Единственный способ, которым dtor может видеть буферы, - это если я объявляю их как переменные класса, однако они используются только в классе ctor.

Пример:

class Input
{
private:
    PSOMETYPE buffer1;
public:
    Input();
    ~Input();
}

Input::Input() : buffer1(NULL)
{
    for(blahblah)
    {
        buffer1 = (PSOMETYPE)malloc(sizeof(SOMETYPE));
        // Do work w/buffer1
        if(buffer1 != NULL) { free(buffer1); buffer1 = NULL }
    }
}

Input::~Input()
{
    if(buffer1 != NULL) { free(buffer1); buffer1 = NULL }
}

Учитывая, что я использую только буфер в ctor, имеет ли смысл объявлять его как переменную частного класса? Если я объявлю это в рамках ctor, dtor не будет знать, что это такое.

Я знаю, что это тривиальный пример, и, честно говоря, я мог бы реализовать это так же легко, как забыть об использовании умного указателя для ссылки на мой класс и иметь пустой dtor, просто free (), как я делаю внутри цикла. У меня нет наставника или учебы, и я не уверен, когда следует придерживаться идиомы RAII.

1 Ответ

6 голосов
/ 13 марта 2012

Смысл RAII состоит в том, чтобы использовать локальный объект для управления локально размещенным объектом, а не искусственно привязывать его время жизни к строящемуся объекту:

class Input
{
    // no pointer at all, if it's only needed in the constructor
public:
    Input();
    // no explicit destructor, since there's nothing to explicitly destroy
};

Input::Input()
{
    for(blahblah)
    {
        std::unique_ptr<SOMETYPE> buffer1(new SOMETYPE);

        // or, unless SOMETYPE is huge, create a local object instead:
        SOMETYPE buffer1;

        // Do work w/buffer1
    }   // memory released automatically here
}

Вы должны когда-либо использовать delete (или free, или что-то еще) самостоятельно, только если вы пишете класс, целью которого является управление этим ресурсом - и обычно уже есть стандартный класс (такой как смарт) указатель или контейнер), который делает то, что вы хотите.

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

{
    Input i1;     // allocates a buffer, holds a pointer to it
    Input i2(i1); // copies the pointer to the same buffer
}                 // BOOM! destroys both objects, freeing the buffer twice

Самый простой способ предотвратить это - удалить операции копирования, поэтому подобный код не сможет скомпилироваться:

class Input {
    Input(Input const&) = delete;    // no copy constructor
    void operator=(Input) = delete;  // no copy assignment
};

Старые компиляторы могут не поддерживать = delete; в этом случае вы можете получить почти такой же эффект, объявив их конфиденциально без = delete, и не реализовав их.

...