Seguridad de argumentos de función C++


En una función que toma varios argumentos del mismo tipo, ¿cómo podemos garantizar que el llamante no estropee el orden?

Por ejemplo

void allocate_things(int num_buffers, int pages_per_buffer, int default_value ...

Y posteriores

// uhmm.. lets see which was which uhh..
allocate_things(40,22,80,...
 58
Author: πάντα ῥεῖ, 2016-08-08

7 answers

Una solución típica es poner los parámetros en una estructura, con campos con nombre.

AllocateParams p;
p.num_buffers = 1;
p.pages_per_buffer = 10;
p.default_value = 93;
allocate_things(p);

No tiene que usar campos, por supuesto. Puedes usar funciones miembro o lo que quieras.

 70
Author: Dietrich Epp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-08 19:09:26

Si tiene un compilador de C++11, podría usar literales definidos por el usuario en combinación con tipos definidos por el usuario. He aquí un enfoque ingenuo:

struct num_buffers_t {
    constexpr num_buffers_t(int n) : n(n) {}  // constexpr constructor requires C++14
    int n;
};

struct pages_per_buffer_t {
    constexpr pages_per_buffer_t(int n) : n(n) {}
    int n;
};

constexpr num_buffers_t operator"" _buffers(unsigned long long int n) {
    return num_buffers_t(n);
}

constexpr pages_per_buffer_t operator"" _pages_per_buffer(unsigned long long int n) {
    return pages_per_buffer_t(n);
}

void allocate_things(num_buffers_t num_buffers, pages_per_buffer_t pages_per_buffer) {
    // do stuff...
}

template <typename S, typename T>
void allocate_things(S, T) = delete; // forbid calling with other types, eg. integer literals

int main() {
    // now we see which is which ...
    allocate_things(40_buffers, 22_pages_per_buffer);

    // the following does not compile (see the 'deleted' function):
    // allocate_things(40, 22);
    // allocate_things(40, 22_pages_per_buffer);
    // allocate_things(22_pages_per_buffer, 40_buffers);
}
 30
Author: sergej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-15 10:11:09

Dos buenas respuestas hasta ahora, una más: otro enfoque sería intentar aprovechar el sistema de tipos siempre que sea posible, y crear tipos fuertes. Por ejemplo, usando boost strong typedef (http://www.boost.org/doc/libs/1_61_0/libs/serialization/doc/strong_typedef.html).

BOOST_STRONG_TYPEDEF(int , num_buffers);
BOOST_STRONG_TYPEDEF(int , num_pages);

void func(num_buffers b, num_pages p);

Llamar a func con argumentos en el orden incorrecto sería ahora un error de compilación.

Un par de notas sobre esto. Primero, el fuerte typedef de boost es bastante anticuado en su enfoque; puedes hacer mucho cosas más agradables con variadic CRTP y evitar macros por completo. En segundo lugar, obviamente esto introduce algunos gastos generales, ya que a menudo tiene que convertir explícitamente. Así que generalmente no quieres abusar de ella. Es muy agradable para las cosas que surgen una y otra vez en su biblioteca. No es tan bueno para las cosas que surgen como una salida. Así, por ejemplo, si está escribiendo una biblioteca GPS, debe tener un fuerte doble typedef para distancias en metros, un fuerte typedef int64 para tiempo pasado época en nanosegundos, y así sucesivamente.

 30
Author: Nir Friedman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-30 21:47:12

(Nota: post fue etiquetado originalmente 'C')

C99 en adelante permite una extensión a @Dietrich Epp idea: compuesto literal

struct things {
  int num_buffers;
  int pages_per_buffer;
  int default_value 
};
allocate_things(struct things);

// Use a compound literal
allocate_things((struct things){.default_value=80, .num_buffers=40, .pages_per_buffer=22});

Podría incluso pasar la dirección de la estructura.

allocate_things(struct things *);

// Use a compound literal
allocate_things(&((struct things){.default_value=80,.num_buffers=40,.pages_per_buffer=22}));
 9
Author: chux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:53:53

No se puede. Es por eso que se recomienda tener el menor número posible de argumentos de función.

En tu ejemplo podrías tener funciones separadas como set_num_buffers(int num_buffers), set_pages_per_buffer(int pages_per_buffer) etc.

Probablemente te hayas dado cuenta de que allocate_things no es un buen nombre porque no expresa lo que la función está haciendo realmente. Especialmente no esperaría que establezca un valor predeterminado.

 7
Author: Frank Puffer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-08 19:12:24

Solo para completar, puede usar argumentos con nombre, cuando su llamada se convierte.

void allocate_things(num_buffers=20, pages_per_buffer=40, default_value=20);
// or equivalently
void allocate_things(pages_per_buffer=40, default_value=20, num_buffers=20);

Sin embargo, con el C++ actual esto requiere bastante código para ser implementado (en el archivo de encabezado declarando allocate_things(), que también debe declarar los objetos externos apropiados num_buffers, etc., proporcionando operator= que devuelven un objeto adecuado único).

---------- ejemplo de trabajo (para sergej)

#include <iostream>

struct a_t { int x=0; a_t(int i): x(i){} };
struct b_t { int x=0; b_t(int i): x(i){} };
struct c_t { int x=0; c_t(int i): x(i){} };

// implement using all possible permutations of the arguments.
// for many more argumentes better use a varidadic template.
void func(a_t a, b_t b, c_t c)
{ std::cout<<"a="<<a.x<<" b="<<b.x<<" c="<<c.x<<std::endl; }
inline void func(b_t b, c_t c, a_t a) { func(a,b,c); }
inline void func(c_t c, a_t a, b_t b) { func(a,b,c); }
inline void func(a_t a, c_t c, b_t b) { func(a,b,c); }
inline void func(c_t c, b_t b, a_t a) { func(a,b,c); }
inline void func(b_t b, a_t a, c_t c) { func(a,b,c); }

struct make_a { a_t operator=(int i) { return {i}; } } a;
struct make_b { b_t operator=(int i) { return {i}; } } b;
struct make_c { c_t operator=(int i) { return {i}; } } c;

int main()
{
  func(b=2, c=10, a=42);
}
 7
Author: Walter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-10 21:58:45

¿Realmente vas a intentar QA todas las combinaciones de enteros arbitrarios? Y tirar en todos los controles de valores negativos / cero, etc?

Simplemente cree dos tipos de enumeración para el número mínimo, medio y máximo de búferes, y tamaños de búfer pequeños, medianos y grandes. Luego deje que el compilador haga el trabajo y deje que sus amigos de QA se tomen una tarde libre:

allocate_things(MINIMUM_BUFFER_CONFIGURATION, LARGE_BUFFER_SIZE, 42);

Entonces solo tienes que probar un número limitado de combinaciones y tendrás una cobertura del 100%. Las personas que trabajan en tu código 5 dentro de unos años solo tendrán que saber lo que quieren lograr y no tener que adivinar los números que podrían necesitar o qué valores se han probado realmente en el campo.

Hace que el código sea un poco más difícil de extender, pero suena como que los parámetros son para un ajuste de rendimiento de bajo nivel, por lo que girar los valores no debe percibirse como barato/trivial/sin necesidad de pruebas exhaustivas. Una revisión de código de un cambio de asignado_alguna cosa(25, 25, 25);

To a

Asignate_something(30, 80, 42);

...es probable que solo se haga un encogimiento de hombros, pero una revisión de código de un nuevo valor de enumeración EXTRA_LARGE_BUFFERS probablemente desencadenará todas las discusiones correctas sobre el uso de la memoria, la documentación, las pruebas de rendimiento, etc.

 6
Author: Dan Haynes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-10 00:44:27