Мне нужно работать с парой устаревших C-функций, которые, как обычно, имеют параметры ввода, ввода-вывода и вывода, и я хочу обернуть их в функцию C ++.
(я использовал double
в качестве параметров для простоты здесь, но я думаю, что вопрос также действителен для аргументов, включающих тяжелый struct
со многими членами.)
Функция C выглядит следующим образом:
void Cfun(double* in_a, double* io_b, double* out_c, double* in_destr_d){}
В документации говорится, что первый параметр a
является входным параметром, b
является вводом-выводом, c
является выводом и d
является входным параметром, который при возврате будет содержать неопределенный (мусор)значение.(и все указатели должны быть ненулевыми, и берется только один элемент, поэтому массивы не используются.)
В C ++ очевидно, что можно реорганизовать Cfun
без потерь следующим образом:
double fun0(double a, double& b, double d){
double c;
Cfun(const_cast<double*>(&a), &b, &c, &d);
return c;
}
Что должно работать, но также будет включать копии, которых в оригинальной версии C не было.Итак, учитывая наши знания Cfun
(документация + мы знаем, что мы делаем), лучшей версией может быть:
double fun1(double const& a, double& b, double& d){
double c;
Cfun(const_cast<double*>(&a), &b, &c, &d);
return c;
}
То есть a
повышается до константной ссылки, b
и d
повышается до ссылки, а c
повышается до возвращаемого значения.
Эта функция может использоваться следующим образом:
double a = 1.1;
double b = 1.2;
double d = 1.3;
i)
...
double c1 = fun1(1.1, b, d);
ii)
...
double c1 = fun1(a , b, d);
// b has useful information here
// d has garbage
То есть вторым и третьим аргументом должны быть l-значения, что нормально.
Однако между параметрами существует небольшая разница b
и d
.Хотя b
имеет полезную информацию, d
по существу уничтожен, и использование его значения после вызова функции будет ошибкой в любом случае.
Эта ситуация напомнила мне ссылку на r-значение и перемещение в C ++11.Где перемещенные параметры r-значения оказываются в неопределенных, но назначаемых состояниях.Это заставляет меня думать, что параметр ввода в мусор в C может быть более или менее сопоставлен со ссылкой на r-значение.
Другими словами, обертка может быть написана таким образом, чтобы передать, что значениеd
, не только изменится, но и останется в непригодном для использования состоянии.
double fun2(double const& a, double& b, double&& d){
double c;
Cfun(const_cast<double*>(&a), &b, &c, &d);
return c;
}
Это звучит разумно, я что-то теряю, определяя эту обертку?Практикуется ли что-то подобное в аналогичном контексте?Оправдывает ли это значение r для встроенного типа (например, double
здесь)?
Небольшая проблема, которую я нахожу, состоит в том, что синтаксис станет более подробным, хотя и более четким с точки зренияс точки зрения удобства использования d
после вызова функции.
Насколько я сейчас вижу, возможные синтаксисы изменяются на:
i)
...
// double c = fun2(1.1, b, d); // now compile error, d is not a r-value
double c = fun2(1.1, b, std::move(d));
// obvious error to use the value of d, since it was (probably) moved. However "moving a POD??"
d = 2.; // assignment is ok
ii)
...
double c = fun2(1.1, b, 1.3); // good, I can pass a literal now, nothing to misuse
iii)
...
double c = fun2(1.1, b, double{d}); // not clear, reaction "this seems a redudant cast"
iv)
...
double c = fun2(1.1, b, copy(d)); // verbose, but "d should be fine because it was copied"
где template<class T> T copy(T&& t){return t;}
В итоге, этоfun2
лучшая оболочка для Cfun
, чем fun1
?
Что если double
будет заменен структурой с 29 переменными-членами (и не хотят копировать их) во всехпримеры выше?