2013-12-05 3 views
8

Я видел комментарии или ответы на SoF о том, что перегрузка оператора трансляции на bool опасна, и вместо этого я должен предпочесть void* operator. Тем не менее, я хотел бы спросить, является ли опасная практика использовать этот оператор в моем случае использования, и если да, то почему.Опасно ли перегружать оператор bool в этом случае

я реализую просто геометрия библиотеки и один из его самых основных классов является точка, что я определяю следующим образом:

struct point { 
    double x, y; 
    bool real; 
    point(double x_, double y_): x(x_), y(y_), real(true) {} 
    point(): x(0), y(0), real(true) {} 

    operator bool() { 
    return real; 
    } 
}; 

Я попытаюсь объяснить, почему мне нужен оператор приведения к BOOL. У меня есть еще один класс называется линия, и я функция, объявленная следующим образом:

point intersect(const line& a, const line& b); 

Теперь перегрузив оператор приведения к BOOL точки я могу написать как: point p = intersect(a,b); и if (intersect(a,b)) таким образом, либо получать точку пересечения двух линий или проверить, пересекаются ли две линии. Я использую это и в других местах, но я считаю, что этого примера достаточно, чтобы показать использование оператора трансляции. Является ли мое использование оператора трансляции опасным, и если бы вы могли привести пример, когда эффект не будет таким, как ожидалось?

EDIT: одно небольшое дополнение благодаря juanchopanza: в этом проекте я ограничен не использованием C++ 11, поэтому я не могу сделать оператор явным.

+0

Если у вас есть C++ 11, вы можете удалить опасность, сделав оператор трансляции «явным». – juanchopanza

+0

@juanchopanza Я знаю, но я ограничен pre-'++ 11' –

+1

Если вы не можете потрудиться, чтобы реализовать свой собственный' safe_bool', см. Этот ответ для набора, доступного в boost: http://stackoverflow.com/ вопросы/11781584/safe-bool-idiom-in-boost – Nim

ответ

5

Это не достаточно. Поскольку вы застряли с C++ 03, safe-bool idiom - это то, что вы должны использовать, если вам вообще нужна эта вещь (в противном случае предпочтительной должна быть функция преобразования explicit).

Альтернативным решением является возвращение boost:optional вместо:

boost::optional<point> intersect(const line& a, const line& b); 

Я хотел бы принять этот подход (даже в C++ 11), так как она выглядит семантически лучше — параллельные линии не имеют точки пересечения, так возвращаемое значение должно действительно быть опционально (или возможно значение).

+0

Я думаю, что благодаря ссылке в вашем ответе я понимаю, почему перегрузка bool опасна. Но будет ли «пустота» 'лучше в этом смысле? –

+0

@IvayloStrandjev: Пожалуйста, перейдите по ссылке и поймите, почему она называется * safe * bool idiom. – Nawaz

+0

Я не могу понять, почему реализация безопасной идиомы bool, как предлагается, делает попытку написать 'p1

3

Да, это опасно. Например, давайте рассмотрим ваш point точно как есть, без определения operator+. Несмотря на это отсутствие, если мы пытаемся добавить два point объекты, компилятор не приемлю вообще:

point a, b; 

std::cout << a + b; 

Это работает путем преобразования каждого point в bool, затем добавить bool с. В целочисленном контексте false преобразует в 0 и true конвертирует в 1, поэтому мы можем ожидать, что вышеприведенный код будет распечатан 2. Конечно, я просто использовал дополнение в качестве примера. Вы могли бы также делать вычитание, умножение, деление, побитовые операции, логические операции и т. Д. Все они будут компилироваться и выполняться, но, очевидно, производят бесполезные результаты. Аналогичным образом, если мы передадим point некоторой функции, которая принимает только числовой тип, компилятор не остановит s или не пожалуется - он просто преобразуется в bool, и функция получит либо , либо 1 в зависимости от того, была ли real верно или нет.

Безопасная-BOOL идиомы безопасна (ну, менее опасно, так или иначе), потому что вы можете проверить в логическом контексте с void *, но void * не будет неявно преобразовать много что-нибудь еще, как bool воли.Вы (в основном) не можете случайно сделать арифметические операции, побитовые операции и т. Д. На них.


1. Есть еще несколько отверстия в любом случае, в основном с участием называя что-то другое, что делает какой-то явного преобразования на void *. Возможность маркировать операторы преобразования explicit лучше, но, честно говоря, они дают гораздо больше улучшения в читаемости, чем безопасность.

+0

Вы предполагаете, что идиома safe-bool использует 'void *'? – Nawaz

+0

@Nawaz: Есть много разных вещей, которые были названы этим именем на протяжении многих лет, некоторые используют преобразование в 'void *', некоторые перегружают сравнение с 'bool', некоторые делают даже более странные вещи. Однако преобразование в 'void *', вероятно, является наиболее распространенным. –

+1

Ссылка в ответе Наваза дает пример, почему бы не использовать 'void *'. 'a

2

Странно, что эти люди не говорили вам почему перегрузки operator bool() опасно.

Причина в том, что C++ имеет неявное преобразование от bool ко всем другим числовым типам. Поэтому, если вы перегрузите operator bool(), вы потеряете много проверок типа, которые обычно ожидают пользователи класса. Они могут поставить point в любом месте, где ожидается int или float.

Перегрузки operator void*() менее опасно, потому что там меньше, что void* преобразует, но по-прежнему имеет тот же фундаментальный вопрос о том, что void* тип используется для других вещей.

Идея безопасной идиомы bool заключается в возврате типа указателя, который не преобразует в что-либо, используемое в любом API.

Обратите внимание, что вам не о чем беспокоиться, если вы были готовы написать if (intersect(a,b).real) или, возможно, if (intersect(a,b).exists()). C++ 03 имеют явные преобразования. Любая функция с одним параметром (или нестатическая функция-член без параметров) представляет собой преобразование сорта. C++ 03 просто не имеет явного преобразования операторов.

+2

Мне все еще не нравится идея 'real' или' exists() '. 'optional ' или 'возможно ' было бы лучше. – Nawaz

+0

@Nawaz: конечно, в этом случае странно определять тип, представляющий то, что может или не может быть точкой. Но, как правило, если у вас будет объект, который может быть в состоянии ошибки, я думаю, что люди чрезмерно привержены идее, что вы должны проверить это состояние, запустив объект в булевом контексте. Вам разрешено решить, что это слишком много хлопот, чтобы реализовать :-) Если вы хотите расширить эвклидову плоскость, вы можете сделать ее «пересекающейся (a, b) .isfinite()». –

0

К примеру, в начале стандартного класса станда C++ 2003 :: basic_ios имел следующий оператор преобразования

operator void*() const 

Теперь, когда новый C++ Standard имеет ключевое слово явного этот оператор был заменен на

explicit operator bool() const; 

Итак, если вы добавите ключевое слово явное для своего оператора, я думаю, что это не будет опасно. :)

Другой способ - определить оператор оператора bool!() В вместо оператора преобразования.

1

Вы можете столкнуться со всеми неприятностями из-за неявных преобразований от bool к числовому значению. Кроме того, ваш класс линии получил (в моей точке vew) опасный член «bool real», чтобы нести результаты вызовов функций. Функция пересечения, возвращающая значение пересечения или NaN, если линии не пересекаются, может решить вашу проблему.

Пример

Имея (бесконечный) класс линии проведения началом р и вектор V:

struct Line { 
    Point p; 
    Vector v; 

    Point operator() (double r) const { 
     return p + r * v; 
    } 
}; 

и функцию вычисления значения пересечения

/// A factor suitable to be passed to line a as argument to calculate the 
/// inersection point. 
/// - A value in the range [0, 1] indicates a point between 
/// a.p and a.p + a.v. 
/// - The result is NaN if the lines do not intersect. 
double intersection(const Line& a, const Line& b) { 
    double d = a.v.x * b.v.y - a.v.y * b.v.x; 
    if(! d) return std::numeric_limits<double>::quiet_NaN(); 
    else { 
     double n = (b.p.x - a.p.x) * b.v.y 
       - (b.p.y - a.p.y) * b.v.x; 
     return n/d; 
    } 
} 

Затем вы можете сделать :

double d = intersection(a, b); 
if(d == d) { // std::isnan is c++11 
    Point p = a(d); 
} 

Или

if(0 <= d && d <= 1) { 
    Point p = a(d); 
} 
0

Хотя уже есть несколько очень хорошие ответы, я хотел бы собрать немного более полный ответ с объяснением, почему предложенное решение работает.

1. Почему мое решение ошибочно? Компилятор выполняет неявные преобразования от bool до числовых типов, что приводит к определению непредвиденных операторов. Например:

point p; 
if (p < 1) 

Будет компилироваться, пока я этого не ожидал. Изменение оператора для преобразования в operator void*() улучшит ситуацию, но лишь незначительно, поскольку некоторые операторы (хотя и меньше, чем для bool), которые определены для void *, и их существование снова приведет к неожиданному поведению.

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

class point { 
    typedef void (point::*bool_type)() const; 
    void not_supported() const; // NOTE: only declaration NO body! 

public: 
    double x, y; 
    bool real; 
    point(double x_, double y_): x(x_), y(y_), real(true) {} 
    point(): x(0), y(0), real(true) {} 

    operator bool_type() { 
    return (real == true)? &point::not_supported : 0; 
    } 

    template<typename T> 
    bool operator==(const T& rhs) const { 
    not_supported(); 
    return false; 
    } 

    template<typename T> 
    bool operator!=(const T& rhs) const { 
    not_supported(); 
    return false; 
    } 
}; 

Теперь сначала я объясню, почему мы должны использовать тип, указатель функции, а не простой указатель. Ответ изложен Стивом Джессопом в комментарии ниже ответа Наваза. function pointers are not < comparable, поэтому любая попытка написать p < 5 или любое другое сравнение между точкой и некоторым другим типом не скомпилируется.

Зачем нам нужен метод без тела (not_supported)? Потому что каждый раз, когда мы пытаемся создать шаблонные методы для сравнения (то есть == и !=), у нас будет вызов функции метода без тела, который вызовет ошибку компиляции.

+0

Ваш класс 'point' должен поддерживать оператор' == ', потому что класс точки должен быть сопоставим с другими точками. И реализация 'operator ==' в wiki-странице не имеет ничего общего с идиомой safe-bool как таковой. Это просто, чтобы продемонстрировать, что идиома safe-bool не преобразуется в 'bool' или что-то, что можно сравнить. – Nawaz

+0

Если я не перегружаю ==, я смог сравнить свой класс с другим классом с аналогичной структурой без ошибки компиляции. Я попробовал это. –

+0

Я этого не говорил. Я сказал, что ваш класс должен поддерживать '=='. Вот что я имел в виду: http://coliru.stacked-crooked.com/a/fb22e2a0baaed5f2 – Nawaz

Смежные вопросы