Управление преобразованием типов в условной операции
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.08.21 16:22
Оценка:
Есть у меня маленький класс Handle, реализующий RAII для виндового типа HANDLE (общий тип для объектов ядра). Сам тип HANDLE определен в винде, как void *. В моем классе, как положено, есть конструктор с параметром типа HANDLE, и метод operator HANDLE.

Все было прекрасно, пока я однажды не написал:

HANDLE Sync = (Timer != 0)? Timer : Event;

где Timer — объект класса Handle, а Event — обычная переменная типа HANDLE.

Я ожидал, что компилятор преобразует Timer к HANDLE, а он, наоборот, преобразует Event к объекту класса Handle, создавая временный объект, и тут же уничтожая его, что приводит к вызову деструктора и закрытию хэндла.

Такая же хрень происходит, если вместо HANDLE использовать int или другой интегральный тип.

Можно ли как-то указать в классе, чтобы в подобных случаях объект преобразовывался к простому типу через соответствующий метод, а не наоборот?
ternary conversion operator
Re: Управление преобразованием типов в условной операции
От: Zhendos  
Дата: 25.08.21 16:36
Оценка: 17 (2) +5
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>HANDLE Sync = (Timer != 0)? Timer : Event;


ЕМ>где Timer — объект класса Handle, а Event — обычная переменная типа HANDLE.


ЕМ>Я ожидал, что компилятор преобразует Timer к HANDLE, а он, наоборот, преобразует Event к объекту класса Handle, создавая временный объект,


Если объявить конструктор вашего класса Handle как explicit, то автоматических преобразований не будет
Re: Управление преобразованием типов в условной операции
От: Videoman Россия https://hts.tv/
Дата: 25.08.21 17:05
Оценка: +1
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Можно ли как-то указать в классе, чтобы в подобных случаях объект преобразовывался к простому типу через соответствующий метод, а не наоборот?


Как уже подсказали, у тебя проблема в дизайне обертки и архитектуры класса в целом. Класс обертка Handle (вызывающий закрытие хендла в деструкторе) — владеющий, а просто объект HANDLE — нет. Очень опасно делать класс обертку с конструктором который неявно может принимать HANDLE. Сделай дизайн похожим на unique_ptr, если хендл у тебя не шарится вовсе, типа такого:
    class Handle
    {
    public:

        ////////////////////////////////////////////////////////////////////////////////////////////
        // Construction/Destruction

        explicit Handle(HANDLE = nullptr);
        Handle(Handle&& that) noexcept = default;
       
        ////////////////////////////////////////////////////////////////////////////////////////////
        // Public methods

        void swap(Handle& that) noexcept;
        Handle& operator=(Handle&& that) noexcept = default;

        operator HANDLE() const noexcept;
        HANDLE Detach();
        void Close();

    private:

        ////////////////////////////////////////////////////////////////////////////////////////////
        // Data members

        std::unique_ptr<void, void(*)(HANDLE)> m_handle; // Native handle wrapper
    };



    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Construction/Destruction

    inline Handle::Handle(HANDLE handle)
    :   m_handle(handle, &sys::CloseHandle)
    {
    }



    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Public methods

    inline void Handle::swap(Handle& that) noexcept
    {
        swap(m_handle, that.m_handle);
    }



    ////////////////////////////////////////////////////////////////////////////////////////////////
    inline Handle::operator HANDLE() const noexcept
    {
        return m_handle.get();
    }



    ////////////////////////////////////////////////////////////////////////////////////////////////
    inline HANDLE Handle::Detach() noexcept
    {
        return m_handle.release();
    }



    ////////////////////////////////////////////////////////////////////////////////////////////////
    inline void Handle::Close()
    {
        m_handle.reset();
    }
Или можно без explicit в конструкторе, но тогда уже с ::DuplicateHandle в конструкторе копирования и подсчетом ссылок.
Re: Управление преобразованием типов в условной операции
От: TailWind  
Дата: 25.08.21 18:03
Оценка:
Во дела!

ЕМ>Можно ли как-то указать в классе, чтобы в подобных случаях объект преобразовывался к простому типу через соответствующий метод, а не наоборот?


Мне кажется нет

Только:
HANDLE Sync = (Timer != 0) ? (HANDLE) Timer : Event;
Re: Управление преобразованием типов в условной операции
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 25.08.21 18:29
Оценка: +2
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Я ожидал, что компилятор преобразует Timer к HANDLE, а он, наоборот, преобразует Event к объекту класса Handle, создавая временный объект, и тут же уничтожая его, что приводит к вызову деструктора и закрытию хэндла.


ЕМ>Такая же хрень происходит, если вместо HANDLE использовать int или другой интегральный тип.


ЕМ>Можно ли как-то указать в классе, чтобы в подобных случаях объект преобразовывался к простому типу через соответствующий метод, а не наоборот?


Конструктор Handle надо бы сделать explicit
Маньяк Робокряк колесит по городу
Re: Управление преобразованием типов в условной операции
От: _NN_ www.nemerleweb.com
Дата: 25.08.21 18:30
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

По существу уже подсказали.
Впрочем explicit конструкторы по умолчанию это хороший принцип.

Может возьмёте готовое решение чем писать который раз тот же код ?
https://github.com/microsoft/wil
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[2]: Управление преобразованием типов в условной операции
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.08.21 18:58
Оценка:
Здравствуйте, Zhendos, Вы писали:

Z>Если объявить конструктор вашего класса Handle как explicit, то автоматических преобразований не будет


Спасибо, помогло. Теперь вспомнил, что в разное время несколько раз читал про explicit, но пользоваться не приходилось, и в нужный момент не вспомнилось.
Re[2]: Управление преобразованием типов в условной операции
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.08.21 19:01
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Может возьмёте готовое решение чем писать который раз тот же код ?


Не люблю добавлять зависимости без крайней нужды. Если б какой сложный алгоритм, то имело бы смысл подключить библиотеку, а так я и этот Handle сделал исключительно для вящей красоты.
Re[3]: Управление преобразованием типов в условной операции
От: TailWind  
Дата: 25.08.21 19:39
Оценка: :)
Z>>Если объявить конструктор вашего класса Handle как explicit, то автоматических преобразований не будет

ЕМ>Спасибо, помогло. Теперь вспомнил, что в разное время несколько раз читал про explicit, но пользоваться не приходилось, и в нужный момент не вспомнилось.


Да проще его тогда вообще не объявлять

А присвоения через void operator = (HANDLE q); сделать
Re[4]: Управление преобразованием типов в условной операции
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.08.21 20:59
Оценка:
Здравствуйте, TailWind, Вы писали:

TW>Да проще его тогда вообще не объявлять

TW>А присвоения через void operator = (HANDLE q); сделать

Тогда нельзя будет определить константный объект.
Re: Управление преобразованием типов в условной операции
От: qaz77  
Дата: 30.08.21 12:21
Оценка: +4
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Есть у меня маленький класс Handle, реализующий RAII для виндового типа HANDLE (общий тип для объектов ядра).


А я для этой цели пользуюсь напрямую std::unique_ptr без оберток:

typedef std::unique_ptr<std::remove_pointer<HANDLE>, decltype(&::CloseHandle)> handle_holder_t;
Re[2]: Управление преобразованием типов в условной операции
От: Alexander G Украина  
Дата: 31.08.21 08:49
Оценка: +1
Здравствуйте, qaz77, Вы писали:


Q>А я для этой цели пользуюсь напрямую std::unique_ptr без оберток:


Q>
typedef std::unique_ptr<std::remove_pointer<HANDLE>, decltype(&::CloseHandle)> handle_holder_t;


C++20 путь:

using handle_holder_t = std::unique_ptr<std::remove_pointer<HANDLE>, decltype([](HANDLE h) { ::CloseHandle(h); })>;

Лябмды без состояния конструируемы по умолчанию.
Преимущества:
* не присваивать постоянно указатель на CloseHandle.
* размер: подлежит оптимизации пустого функтора через базу или [no_unique_address]], если unique_ptr её реализует
Русский военный корабль идёт ко дну!
Re[3]: Управление преобразованием типов в условной операции
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 31.08.21 10:17
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>не присваивать постоянно указатель на CloseHandle.


Что значит "не присваивать постоянно"?
Re[3]: Управление преобразованием типов в условной операции
От: Videoman Россия https://hts.tv/
Дата: 31.08.21 11:22
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Лябмды без состояния конструируемы по умолчанию.

AG>Преимущества:
AG>* не присваивать постоянно указатель на CloseHandle.
AG>* размер: подлежит оптимизации пустого функтора через базу или [no_unique_address]], если unique_ptr её реализует

Все классно, только не понятно зачем тут С++20, кроме как для краткости ?!
#include <iostream>
#include <memory>

// Just workaround

using HANDLE = void*;
void CloseHandle(HANDLE handle)
{
    // ...
}

// Handle wrappper

struct HanleDeleter {
        void operator()(HANDLE handle) noexcept { CloseHandle(handle); };
};

using handle_holder_t = std::unique_ptr<std::remove_pointer<HANDLE>, HanleDeleter>;

int main() 
{
    handle_holder_t handle;
    return 0;
}
Re[4]: Управление преобразованием типов в условной операции
От: Alexander G Украина  
Дата: 31.08.21 15:38
Оценка: -1
Здравствуйте, Videoman, Вы писали:


V>Все классно, только не понятно зачем тут С++20, кроме как для краткости ?![ccode]


Да, для краткости, чтобы в одну строчку лямбу написать, а не во внешней структуре.
Русский военный корабль идёт ко дну!
Re[4]: Управление преобразованием типов в условной операции
От: Alexander G Украина  
Дата: 31.08.21 15:41
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:


ЕМ>Что значит "не присваивать постоянно"?


Тип
typedef std::unique_ptr<std::remove_pointer<HANDLE>, decltype(&::CloseHandle)> handle_holder_t;

означает, что в unique_ptr будет использован в качестве удалителя функция с такой же сигнатурой, как ::CloseHandle.

Сам указатель на CloseHandle надо ещё передавать отдельно при конструировании/присвоении.

Это, ну, не очень удобно.
Русский военный корабль идёт ко дну!
Re[5]: Управление преобразованием типов в условной операции
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 31.08.21 20:39
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Сам указатель на CloseHandle надо ещё передавать отдельно при конструировании/присвоении.


А, понятно. Я никогда не пользовался unique_ptr и подобными шаблонами из std — не люблю этого многокаскадного шаманства. Но вместо него можно сделать простенький универсальный шаблонный класс с параметрами типа значения и указателя на функцию, которая вызывается с этим значением. Оптимизатор VC++ даже достаточно умен, чтобы вызывать ее статически, если указатель передан непосредственно.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.