Перегрузка операторов C ++ и неявное преобразование

StackOverflow https://stackoverflow.com/questions/1607678

Вопрос

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

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

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

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

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

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

Это было полезно?

Решение

Предполагая, что вы хотите, чтобы специализированная версия была выбрана для любого целочисленного типа (а не только для 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;
}

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

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

Другие советы

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

Для получения более подробной информации прочтите раздел 13.3 стандарта C ++ 03.Почувствуй боль.

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

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

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

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

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

Я обнаружил, что из этого правила очень мало исключений. (<=> довольно известный.)

Изменить. Извините, я упустил момент, когда не разрешал неявные преобразования из <=> в <=>.

Единственный способ предотвратить это - предоставить операторов <=>.

Как сделать явное явное преобразование ?

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top