Создание необъектного ресурса RAII-совместимого - PullRequest
1 голос
/ 12 октября 2009

в моем коде я использую HANDLE s от windows.h. Они используются как

HANDLE h;
if (!openHandleToSomething(arg1, arg2, &h)) {
    throw std::exception("openHandleToSomething error");
}
/* Use the handle in other functions which can throw as well */
if (!CloseHandle(h)) {
    throw std::exception("closeHandle error");
}

Как видите, вы должны вставить это CloseHandle в каждое исключение, которое может произойти в середине процесса получения и выпуска. Поэтому, скорее всего, вы забудете одно (или есть необычное исключение SEH, о котором вы не знали) и вуаля, у вас утечка памяти.

Недавно я прочитал о RAII, который должен устранить головные боли в таких случаях и должен автоматически вызывать это CloseHandle. Я также видел, что в C ++ есть что-то вроде std::auto_ptr<someType>, которое решает проблему для ресурсов, которые были выделены с помощью new.

Однако, поскольку я не использую new и поскольку HANDLE просто typedef обозначен как void *, мне интересно, как мне использовать std::auto_ptr<someType>. Каким-то образом должно быть возможно дать ему пользовательскую функцию удаления (if (!CloseHandle(h)) { throw std::exception("closeHandle error"); }). Создание класса было бы другим методом, поскольку деструктор вызывается каждый раз, когда его экземпляр выходит из области видимости. Однако иметь класс для каждой простой вещи просто излишне.

Как я могу исправить эти случайные утечки памяти?

Обратите внимание, что я бы предпочел решения на чистом C ++ без библиотек и больших зависимостей, за исключением случаев, когда они действительно малы и в любом случае используются в большинстве сред.

Ответы [ 6 ]

6 голосов
/ 12 октября 2009

Одна идея, которая приходит в голову, - это использовать boost :: shared_ptr с пользовательским средством удаления .

3 голосов
/ 12 октября 2009

Вы можете реализовать свою собственную простую идиому RAII.

class auto_handle {
public:
    auto_handle() : handle_() {}
    ~auto_handle() {
        if (!CloseHandle(handle_)) {
            // Don't throw here (1), manage the error in other way.
        }
    }
    HANDLE& handle() { return handle_; }
private:
    auto_handle(const auto_handle&);
    auto_handle& operator=(const auto_handle&);
    HANDLE handle_;
};

(1) Вы никогда не должны бросать из деструктора .

auto_handle h;
if (!openHandleToSomething(arg1, arg2, &h.handle())) {
    throw exception("openHandleToSomething error"); // Now it is safe
}
2 голосов
/ 12 октября 2009

1) Не используйте auto_ptr<>. Шутки в сторону. Вам не нужны эти головные боли - слишком легко проскальзывать, потому что у них нет знакомой семантики копирования.

2) Оберните HANDLE простым объектом, который предоставляет средство доступа, которое дает вам основной дескриптор. Это необходимо для передачи HANDLE в вызовы API. (Я бы посчитал метод доступа предпочтительнее неявного преобразования.)

3) Я никогда не задумывался над упаковкой HANDLE, поэтому я не знаю, есть ли какие-нибудь удивительные ошибки. Если они есть, я не могу их указать. Я бы не ожидал, что это непрозрачное значение. Но тогда, кто ожидает удивительную ошибку? В конце концов, это сюрпризы.

4) (конечно) реализовать соответствующий dtor.

1 голос
/ 12 октября 2009

Вам просто нужна простая обертка, которая дает вам дескриптор при передаче ее в функцию:

#include <stdexcept>
class HWrapper
{
    HANDLE h;
    bool   closed;

    public:
        HWrapper(A1 arg1,A2 arg2)
            :closed(false)
        {
            if (!openHandleToSomething(arg1, arg2, &h))
            {    throw std::runtime_error("openHandleToSomething error");
            }
        }
        ~HWrapper()
        {
            try
            {
                if (!closed)
                {   close();
                }
            }
            catch(...) {/*Exceptions should never leave a destructor */ }
            // Though you may want to log somthing.
        }
        void close()
        {
            closed = true;
            // Close can throw an exception.
            if (!CloseHandle(h))
            {    throw std::runtime_error("closeHandle error");
            }
        }

        /*
         * This allows you to just pass it to a function that takes an HANDLE
         * See the function:   functionThatUsesHandleButMayThrow();
         */
        operator HANDLE()
        {
            return h;
        }
    private:
    /*
     * For your use case there is not need to copy.
     * So explicitly disallow copying.
     *
     * Just pass the HWrapper object to any function that requires a handle.
     * The built in cast operator will convert it back to a Handle to be used
     * within these functions. While this object just retains ownership and
     * responcability for deleting the object when you are finished.
     *
     * This allows simple backwards compatibility with existing code.
     */ 
    HWrapper(HWrapper const& copy);            // Don't implement
    HWrapper& operator=(HWrapper const& copy); // Don't implement


};

void functionThatUsesHandleButMayThrow(HANDLE h)
{
}



int main()
{

    try
    {
        HWrapper   w(A1,A2);

        functionThatUsesHandleButMayThrow(w);
        /*
         * If you do not care about close throwing an excepion.
         * Then jsut let it fall out of scope. The destructor
         * will try and clean up. But if it fails it will drop the
         * exception.
         *
         * This is required because if another exception is propogating
         * throwing an exception terminates the application.
         */
    }
    catch(std::exception const& e)
    {
        std::cout << "Exception: " << e.what() << "\n";
    }


    try
    {

        HWrapper   w2(A1,A2);

        functionThatUsesHandleButMayThrow(w2);
        /*
         * If you do care abou the exception
         * The call close() manually. The exception will be thrown.
         *
         * But if an exception is already been thrown in
         * functionThatUsesHandleButMayThrow() then we will try and close it
         * in the destructor and not throw another exception.
         */
        w2.close();
    }
    catch(std::exception const& e)
    {
        std::cout << "Exception: " << e.what() << "\n";
    }
}
1 голос
/ 12 октября 2009

std::auto_ptr является не , подходящим для этой ситуации. У него есть свои применения, но это не один из них. Чтобы исправить (вроде) вопрос, поднятый Грегом Д., проблема с auto_ptr заключается не столько в отсутствии семантики указатель , сколько в его довольно странной принадлежности * семантика когда вы его назначаете, вы не получаете копию указателя, а вместо этого передаете указателя (т. е. цессионарий становится новым единственным владельцем ресурса, а цедент больше ничего не имеет).

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

0 голосов
/ 02 апреля 2018
HANDLE h;
if (!openHandleToSomething(arg1, arg2, &h)) {
    throw std::exception("openHandleToSomething error");
}

Вот оно:

auto d = [](decltype(h)* a) { if(a) ::CloseHandle(*a); };
std::unique_ptr<decltype(h), decltype(d)> buf(&h, d);
...