Скажем, у меня есть функция C ++, которая выглядит так:
double myfunction(double a, double b) {
// do something
}
Который я тогда называю так:
double a = 1.0;
double b = 2.0;
double good_r = myfunction(a, b);
double bad_r = myfunction(b, a); // compiles fine
Я хотел бы убедиться, что a
а также b
никогда не предоставляются в неправильном порядке.
Каков наилучший способ обеспечить это в C ++?
Другие языки допускают именованные параметры, например:
double good_r = myfunction(a=a, b=b);
double bad_r = myfunction(a=b, b=a); // mistake immediately obvious
double bad_r = myfunction(b=b, a=a); // compiles fine
Или, возможно, проблему можно частично решить с помощью типов, то есть
double my_type_safe_function(a_type a, b_type b) {
// do something
}
a_type a = 1.0;
b_type b = 2.0;
double good_r = myfunction(a, b);
double bad_r = myfunction(b, a); // compilation error
РЕДАКТИРОВАТЬ: Несколько человек спросили, что я имею в виду под «неправильный порядок». Я имею в виду, что в реальном коде a
а также b
иметь какое-то значение. Например, аргументы могут вместо height
а также width
, Разница между ними очень важна для того, чтобы функция возвращала правильный результат. Однако они оба являются поплавками и имеют одинаковые размеры (то есть длину). Кроме того, нет «очевидного» порядка для них. Человек, пишущий объявление функции, может принять (width, height)
и человек, использующий функцию, может предположить, (height, width)
, Я бы хотел, чтобы это не произошло по ошибке. С двумя параметрами легко быть осторожным с заказом, но в большом проекте и с количеством аргументов до 6 аргументов закрадываются ошибки.
В идеале я хотел бы, чтобы проверки выполнялись во время компиляции, и чтобы не было никакого снижения производительности (то есть в конце дня они рассматриваются как простые старые значения с плавающей точкой или что-то еще).
Как насчет этого:
struct typeAB {float a; float b; };
double myfunction(typeAB p) {
// do something
return p.a - p.b;
}
int main()
{
typeAB param;
param.a = 1.0;
param.b = 2.0;
float result = myfunction(param);
return 0;
}
Конечно, вы можете все еще испортить, когда назначаете свои параметры, но этого риска трудно избежать 🙂
Вариант состоит в том, чтобы иметь одну структуру на «новый» тип, а затем заставить их уйти в оптимизированных сборках с использованием макросов.
Что-то вроде этого (только слегка проверено, так что это может быть далеко):
#define SAFE 0
#if SAFE
#define NEWTYPE(name, type) \
struct name { \
type x; \
explicit name(type x_) : x(x_) {}\
operator type() const { return x; }\
}
#else
#define NEWTYPE(name, type) typedef type name
#endif
NEWTYPE(Width, double);
NEWTYPE(Height, double);
double area(Width w, Height h)
{
return w * h;
}
int main()
{
cout << area(Width(10), Height(20)) << endl;
// This line says 'Could not convert from Height to Width' in g++ if SAFE is on.
cout << area(Height(10), Width(20)) << endl;
}
Я думаю, что вы уже предоставили самое простое решение, используя типы.
Одной из альтернатив может быть использование класса построителя и цепочки методов.
Подобно:
class MyfunctionBuilder {
MyFunctionBuilder & paramA(double value);
MyFunctionBuilder & paramB(double value);
double execute();
(...)
}
Который вы бы использовали так:
double good_r = MyFunctionBuilder().paramA(a).paramB(b).execute();
Но это много лишнего кода для написания!
Что такое «неправильный порядок» на самом деле? В этом вашем примере
double myfunction(double a, double b) {
// do something
}
double a = 1.0;
double b = 2.0;
double good_r = myfunction(a, b);
double bad_r = myfunction(b, a);
как вы на самом деле хотите знать, если это является правильный порядок? Что если переменные будут называться «quapr» и «moo» вместо «a» и «b»? Тогда было бы невозможно угадать, является ли порядок правильным или неправильным, просто взглянув на них.
Имея это в виду, вы можете сделать по крайней мере две вещи. Во-первых, это дать значимые имена аргументам, например,
float getTax( float price, float taxPercentage )
вместо
float getTax( float a, float b )
Во-вторых, выполните необходимые проверки внутри:
float divide( float dividend, float divisor )
{
if( divisor == 0 )
{
throw "omg!";
}
}
Можно выполнять более сложные проверки, такие как создание функтора и установка его параметров в явном виде, но в большинстве случаев это просто усложняет ситуацию без особой пользы.