2009-10-22 4 views
6

У меня есть класс, который инкапсулирует некоторые арифметические вычисления, скажем, вычисления с фиксированной точкой. Мне нравится идея перегрузки арифметических операторов, поэтому я пишу следующее:Перегрузка и неявное преобразование оператора C++

class CFixed 
{ 
    CFixed(int ); 
    CFixed(float); 
}; 

CFixed operator* (const CFixed& a, const CFixed& b) 
{ ... } 

Все работает. Я могу написать 3 * CFixed (0) и CFixed (3) * 10.0f. Но теперь я понимаю, я могу реализовать оператор * с целочисленным операндом гораздо более эффективным. Поэтому я перегружать:

CFixed operator* (const CFixed& a, int b) 
{ ... } 
CFixed operator* (int a, const CFixed& b) 
{ ... } 

Он по-прежнему работает, но теперь CFixed (0) * 10.0f вызовы перегруженной версии, преобразования с плавающей точкой в ​​междунар (и я ожидал, что это конвертировать поплавок CFixed). Конечно, я могу перегрузить также и поплавковые версии, но для меня это комбинаторный взрыв кода. Есть ли способ обхода (или я неправильно проектирую свой класс)? Как я могу сказать компилятору вызывать перегруженную версию оператора * ТОЛЬКО с ints?

+2

С другой стороны, конструкторы, принимающие один параметр (и, тем более, встроенный), вероятно, должны быть объявлены явным, что, конечно же, предотвратит продвижение ... но также предотвратит ошибки. –

+1

Другой «явный» защитник =) Неявные конструкторы полезны, если вы понимаете, что они делают. Замечательно создавать функцию, принимать CFixed аргумент и передавать целые числа! – SadSido

+0

Я не верю, что вы можете. – atomice

ответ

1

Предполагая, что вы хотите специализированную версию, чтобы быть выбраны для любого интегрального типа (а не только INT, в частности, одна вещь, которую вы могли бы сделать, это обеспечить, что в качестве функции шаблона и использовать Boost.EnableIf для удаления эти перегрузки из имеющегося набора перегрузки, если операнд не является целочисленным типом.

#include <cstdio> 
#include <boost/utility/enable_if.hpp> 
#include <boost/type_traits/is_integral.hpp> 

class CFixed 
{ 
public: 
    CFixed(int ) {} 
    CFixed(float) {} 
}; 

CFixed operator* (const CFixed& a, const CFixed& ) 
{ puts("General CFixed * CFixed"); return a; } 

template <class T> 
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* (const CFixed& a, T ) 
{ puts("CFixed * [integer type]"); return a; } 

template <class T> 
typename boost::enable_if<boost::is_integral<T>, CFixed>::type operator* (T , const CFixed& b) 
{ puts("[integer type] * CFixed"); return b; } 


int main() 
{ 
    CFixed(0) * 10.0f; 
    5 * CFixed(20.4f); 
    3.2f * CFixed(10); 
    CFixed(1) * 100u; 
} 

Естественно, что вы могли бы ALS o использовать другое условие, чтобы сделать эти перегрузки доступными, только если T = int: typename boost::enable_if<boost::is_same<T, int>, CFixed>::type ...

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

+0

I haven ' t пробовал это еще, но не должен ли компилятор предпочитать не шаблонные версии функции до шаблонных? Я думаю, что ваш «главный» будет называть «General CFixed» версии 4 раза ... – SadSido

+0

... похоже, что я недопонимаю enable_if немного ... – SadSido

+0

Он предпочтет шаблоны, если они будут лучше соответствовать. enable_if делает кандидатов перегрузки только в том случае, если условие выполнено, а в случае с float только первая перегрузка является единственной, на которую можно выбрать. – UncleBens

0

Как насчет преобразования explicit?

+0

Как это поможет? Плакат хотел, чтобы CFixed (float) вызывался, если float был передан оператору * - добавление явного ключевого слова сделает это еще менее вероятным. – atomice

3

Если у вас есть конструкторы, которые могут быть вызваны только одним аргументом, вы фактически создали неявный оператор преобразования. В вашем примере, где требуется CFixed, могут быть переданы как int, так и float. Это, конечно, опасно, потому что компилятор может без проблем генерировать код, вызывающий неправильную функцию, вместо того, чтобы лаять на вас, когда вы забыли включить объявление некоторой функции.

Поэтому хорошее эмпирическое правило гласит, что всякий раз, когда вы пишете конструкторы, которые могут быть вызваны только одним аргументом (обратите внимание, что этот foo(int i, bool b = false) может быть вызван одним аргументом, даже если он принимает два аргумента), вы должны сделать этот конструктор explicit, если вы действительно не хотите, чтобы неявное преобразование было введено. explicit конструкторы не используются компилятором для неявных преобразований.

Вы должны изменить свой класс на это:

class CFixed 
{ 
    explicit CFixed(int ); 
    explicit CFixed(float); 
}; 

Я обнаружил, что существует очень мало исключений из этого правила. (std::string::string(const char*) является довольно известным один.)

Edit: Мне очень жаль, что я пропустил пункт о не позволяет неявное преобразование из int в float.

Единственный способ, который я вижу для предотвращения этого, - предоставить операторы для float.

+1

Не вопрос. Как я могу писать Fixed * 10, если мой конструктор явно? – SadSido

+0

@SadSido: Извините. Я добавил свое мнение об этом как о редактировании. – sbi

+0

Спасибо! Ваше редактирование гораздо более полезно (хотя немного пессимистично =) – SadSido

0

Согласен с sbi, вы должны определенно сделать ваши однопараметрические конструкторы явными.

Вы можете избежать взрыва в операторе <> функции вы пишете с шаблонами, однако:

template <class T> 
CFixed operator* (const CFixed& a, T b) 
{ ... } 

template <class T> 
CFixed operator* (T a, const CFixed& b) 
{ ... } 

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

+0

А что будет внутри шаблонов? :-) Точно так же! –

+0

Я думаю, вы поймали меня в середине редактирования. То же самое, что и что? – Bill

+0

@Pavel: На самом деле, я думаю, что Билл прав (даже думал, что он, возможно, не осознал это:). Посмотрите на это: 'template CFixed operator * (const CFixed & a, T b) {return a * CFixed (b); } 'Не должен делать то, что хочет OP? – sbi

4

Необходимо также перезагрузить float. Преобразование от int к указанному пользователем типу (CFixed) имеет более низкий приоритет, чем встроенное преобразование с плавающим интегралом в float.Поэтому компилятор всегда будет выбирать функцию с int, если вы не добавите функцию с float.

Подробнее см. Раздел 13.3 стандарта C++ 03. Почувствуй боль.

Кажется, что я тоже потерял его. :-(UncleBens сообщает, что добавление float только не решает проблему, так как добавлена ​​версия с double. Но в любом случае добавление нескольких операторов, связанных со встроенными типами, утомительно, но не приводит к комбинаторному подталкивание.

+0

. ... и вдвое? – UncleBens

+0

@UncleBens, 'double' будет преобразован в' float'. –

+1

Это говорит о «неоднозначной перегрузке» для меня. Попытка 'CFixed (0) * 1.0', Comeau сообщает мне, в частности, что' 'оператор * (CFixed, int)' и 'operator * (CFixed, float)' равны кандидатам для CFix * double ' – UncleBens

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