Управление неявным преобразованием типов в C ++
-
20-08-2019 - |
Вопрос
Я работаю над кодом, который выполняет запросы ближайшего соседа.Есть две простые идеи, лежащие в основе того, как пользователь может запрашивать данные в поиске:
- ближайшие N точек к данной точке в пространстве.
- все точки в пределах заданного расстояния.
В моем коде точки помещаются в pointList, а pointList - это контейнер, задачей которого является отслеживание точек, найденных при поиске.
Прямо сейчас мой объект pointList имеет один конструктор:
PointList( unsigned int maxvals ); // #1
Следующие два конструктора, которые я хотел бы добавить, следующие:
PointList( float maxdist ); // #2
PointList( unsigned int maxvals, float maxdist ); // #3
Мой вопрос заключается в следующем:как мне гарантировать, что мои пользователи и компилятор C ++ сгенерируют правильный конструктор для списка точек и проводит различие между конструкторами 1 и 2?Должен ли я просто реализовать # 3 и предоставить константы, которые определяют произвольно большие значения для maxvals и maxdist?Другой альтернативой могло бы быть написание другой системы легковесных объектов, которые управляют логикой добавления точек в список, но это кажется излишеством для такой простой идеи.
Я действительно пытаюсь сделать это прозрачным для моих пользователей, которые в основном являются учеными, которые изучали C ++ иногда без получения формального образования.Спасибо!
Решение
Разрешение перегрузки для целочисленных типов происходит по двум категориям, которые можно очень грубо свести к
- Продвижение по службе:Это преобразование из типов, меньших, чем
int
Дляint
илиunsigned int
, в зависимости от того , является лиint
может хранить все значения исходного типа. - Преобразование:Это преобразование из любого целочисленного типа в другой целочисленный тип.
Аналогично, преобразование для типов с плавающей запятой происходит в двух категориях
- Продвижение по службе:Это преобразование из
float
Дляdouble
- Преобразование:Это преобразование из любого типа с плавающей запятой в другой тип с плавающей запятой
И происходит преобразование из целого числа в плавающее или обратно.Это оценивается как конверсия, а не как продвижение по службе.Продвижение оценивается лучше, чем конверсия, и там, где требуется только продвижение, этот вариант будет предпочтительнее.Таким образом, вы можете использовать следующие конструкторы
PointList( int maxVals );
PointList( unsigned int maxVals );
PointList( long maxVals );
PointList( unsigned long maxVals );
PointList( double maxDist );
PointList( long double maxDist );
Для любого целочисленного типа при этом должна быть выбрана первая группа конструкторов.И для любого типа с плавающей запятой это должно выбрать вторую группу конструкторов.Ваши исходные два конструктора могут легко привести к двусмысленности между float
и unsigned int
, если вы пройдете int
, например.Для другого конструктора с двумя аргументами вы можете использовать свое решение, если хотите.
Тем не менее, я бы тоже использовал заводскую функцию, потому что, на мой взгляд, выбор типа значения параметра довольно хрупок.Большинство людей ожидали бы, что следующий результат будет равен
PointList p(floor(1.5));
PointList u((int)1.5);
Но это привело бы к другому положению дел.
Другие советы
Почему бы не использовать фабричные методы вместо конструкторов?Заводские методы обладают преимуществом настраиваемых имен.
static PointList createNearestValues(unsigned int maxvals) {}
static PointList createByDistance(float maxdist) {}
Рассмотрите возможность использования истинные определения типов.Это требует немного больше усилий со стороны вашего клиентского кода, но корректность вам гарантирована.
Вызовите pointList(10) для первого и pointList(10f) для второго.
Для второго вы также можете использовать версию 10.0.
Если присутствуют конструкторы # 1 и # 2, будет вызван правильный конструктор, если вставляемое вами значение имеет значение float или int и преобразование не должно выполняться.Поэтому просто убедитесь, что вы сделали типы чисел, которые вы используете для вызова, явными (т.е.1f и 1).Конструктор # 3, похоже, не очень подходит, поскольку на самом деле в нем нет необходимости и он просто запутал бы пользователей вашего кода.Если вам нужны значения по умолчанию для любого из чисел, вы могли бы использовать
PointList(int max, float max=VALUE)
и
PointList(float max, int max=VALUE)
Снова:это, по-видимому, наносит больше вреда, чем код, с точки зрения удобочитаемости кода.
Это требует хорошего прочтения по Разрешение перегрузки.
Я бы определенно использовал явный конструкторы.В приведенном примере целое число без знака не преобразуется неявно.
class A
{
public:
explicit A(float f){}
explicit A(int i){}
};
void test(){
unsigned int uinteger(0);
A a1(uinteger); //Fails, does not allow implicit conversions
A a2((float)uinteger); //OK, explicit conversion
float f(0.0);
A a3(f); //OK
int integer(0);
A a4(integer); //OK
}
Сообщение об ошибке достаточно легко понять:
: error C2668: 'A::A' : ambiguous call to overloaded function
: could be 'A::A(int)'
: or 'A::A(float)'