Почему «unspecified_bool» для классов, которые имеют внутренние преобразования в их обернутый тип, терпит неудачу? - PullRequest
3 голосов
/ 07 декабря 2009

Я недавно прочитал статью safe bool . Я видел, как эта техника использовалась несколько раз, но никогда не понимал, почему она работает, или именно поэтому она была необходима (вероятно, как и многие, я понял ее суть: просто с помощью operator bool () const позволили некоторые неявные преобразования типа shenanigans, но детали были для меня всегда немного туманными).

Прочитав эту статью, а затем рассмотрев некоторые из ее реализаций в Boost shared_ptr.hpp, я подумал, что справился с этим. Но когда я решил реализовать его для некоторых классов, которые мы заимствовали и расширили или разработали с течением времени, чтобы помочь управлять работой с API-интерфейсами Windows, я обнаружил, что моя наивная реализация не работает должным образом (исходный код компилируется, но использование генерирует ошибка во время компиляции, не найдены правильные преобразования).

Реализации Boost изобилуют условиями для различных уровней поддержки C ++ компиляторами. От использования наивного оператора bool () const до использования указателя на функцию-член и использования указателя на данные-член. Из того, что я понял, указатель на данные члена является наиболее эффективным для компиляторов, если они обрабатывают IFF, когда они вообще его обрабатывают.

Я использую MS VS 2008 (MSVC ++ 9). И ниже пара реализаций, которые я пробовал. Каждое из них приводит к Неоднозначное пользовательское преобразование или оператор не найден .

template<typename HandlePolicy>
class AutoHandleTemplate
{
public :
    typedef typename HandlePolicy::handle_t handle_t;
    typedef AutoHandleTemplate<HandlePolicy> this_type;
    {details omitted}
    handle_t get() const { return m_handle; }
    operator handle_t () const { return m_handle; }

#if defined(NAIVE)
    // The naive implementation does compile (and run) successfully    
    operator bool () const { return m_handle != HandlePolicy::InvalidHandleValue(); }
    bool operator ! () const { return m_handle == HandlePolicy::InvalidHandleValue(); }
#elif defined(FUNC_PTR)    
    // handle intrinsic conversion to testable bool using unspecified_bool technique
    typedef handle_t (this_type::*unspecified_bool_type)() const;
    operator unspecified_bool_type() const // never throws
    {
        return m_handle != HandlePolicy::InvalidHandleValue() ? &this_type::get() : NULL;
    }
#elif defined(DATA_PTR)

    typedef handle_t this_type::*unspecified_bool_type;
    operator unspecified_bool_type() const // never throws
    {
        return m_handle != HandlePolicy::InvalidHandleValue() ? &this_type::m_handle : NULL;
    }
#endif
private :
    handle_t m_handle;
{details omitted}
};

А вот фрагмент кода, который либо работает (наивная реализация), либо ошибки (любой из методов unspecified_bool, выше):

// hModule is an AutoHandleTemplate<ModuleHandlePolicy>
if (!hModule)

и

if (hModule)

Я уже пытался включить оператора! во всех случаях - но хотя первый случай тогда работает, второй не компилируется (неоднозначно).

Мне кажется, что этот класс очень похож на smart_ptr (или auto_ptr). В этом случае он должен поддерживать неявное преобразование в свой базовый тип дескриптора (HMODULE), но он также должен обрабатывать if (instance) и if (! Instance) . Но если я определяю и оператор handle_t, и метод unspecified_bool, я получаю ошибки.

Может кто-нибудь объяснить мне, почему это так, и, возможно, предложить лучший подход? (или я должен довольствоваться наивным подходом, по крайней мере, пока C ++ 0x не будет завершен и явные операторы не будут реализованы в моем компиляторе)?

EDIT:

Может показаться, что ответ вполне может состоять в том, что если я определю неявное преобразование в интеграл, то C ++ будет использовать это преобразование для любых выражений типа if (instance). И что, по крайней мере для вышеприведенного класса, единственная причина для определения любых других операторов (оператор bool) состоит в том, чтобы явно переопределить, используя неявное интегральное преобразование во что-то другое (в вышеприведенном случае, заставляя его сравнивать с INVALID_HANDLE_VALUE вместо неявный NULL).

А использование метода unspecified_bool действительно имеет смысл, только если вы не предоставляете интегральный оператор преобразования?

Ответы [ 3 ]

1 голос
/ 07 декабря 2009
AutoHandleTemplate<ModuleHandlePolicy> hModule( ... );
HMODULE raw_handle = hModule; // if we want to this line works,
// AutoHandleTemplate<ModuleHandlePolicy> should \
//    be implicitly converted to it's raw handle type - HMODULE.

Если один smart-ptr может неявно преобразовываться в его тип необработанного дескриптора, а тип необработанного дескриптора может использоваться в самом булевом тесте, например:

HMODULE the_raw_handle = ...;
if ( the_raw_handle ) {}  // this line is ok

Для этих smart-ptrs нет необходимости (и не следует) определять преобразования в bool, void * или safe_bool, в противном случае - неоднозначность.

operator bool (), void * (), safe_bool () используются для smart-ptrs, которые не могут быть неявно преобразованы в его необработанный дескриптор, или его необработанный дескриптор не может использоваться в логическом контексте.

Попробуйте этот код:

template<typename HandlePolicy>
class AutoHandleTemplate
{
public :
      typedef typename HandlePolicy::handle_t handle_t;
      typedef AutoHandleTemplate<HandlePolicy> this_type;
      {details omitted}

      operator handle_t () const {
            return m_handle==HandlePolicy::InvalidHandleValue()? 0: m_handle;
      }

      // no more conversion functions

private :
      handle_t m_handle;
      {details omitted}
};
0 голосов
/ 08 декабря 2009

Все идиомы отстой, правда.

Лучшее решение:

1) не имеет никаких неявных операторов преобразования

2) есть оператор! переопределить с типом возврата bool. Да, это означает, что некоторые тесты могут быть написаны так, как будто (!! myObject), но это небольшая цена

0 голосов
/ 07 декабря 2009

Неопределенность возникает из-за наличия двух возможных операторов преобразования; либо:

operator handle_t () const;
operator unspecified_bool_type() const;

или

operator handle_t () const;
operator bool () const;

Оба могут быть использованы в логическом выражении, поэтому у вас есть двусмысленность.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...