Anec-notas (I)

Con este post me gustaría comenzar una nueva sección en el blog la cual, por eso de ponerle nombre a todo, he decidido llamar “anec-notas”. Pretendo acá combinar historias técnicas de la semana, problemas resueltos y tópicos que pueda comentar públicamente sin romper cláusulas de confidencialidad, tratando a su vez de hacer un diario personal el cual consultar en un futuro. La idea no es hacer tutoriales completos sobre cada tema, sino contar detalles sobre ellos, así que no haré grandes introducciones en la medida de lo posible. Esta sección se inaugura con una historia de la semana pasada en el trabajo.

Detalles sobre funciones y métodos con parámetros variables

Para usar argumentos variables en C y C++ (antes de la última versión) se escriben unos puntos suspensivos y se usa un tipo de dato especial llamado va_list.

void foo(int arg1, ...)
{
    va_list args;
    va_start(args, arg1);

    float a = va_arg(args, float);
    printf("%f\n", a);

    va_end(args);
}

Básicamente, va_list es un puntero al stack, que es donde están almacenados los parámetros de la función. Con la función va_arg, junto al tipo de dato esperado, se van obteniendo cada uno de los parámetros de forma consecutiva. Por este motivo, es necesario conocer la posición del primer parámetro, por lo que es obligatorio que haya al menos un parámetro formal en la función, el cual se usa en la inicialización.

Todo esto surgió cuando tuve que diseñar una biblioteca de control de cámaras de vídeo. Esta biblioteca debe controlar varios modelos de cámara, cada una con propiedades diferentes. Para simplificar el diseño de la clase, se decidió utilizar dos métodos del tipo SetProperty(int prop, …) y GetProperty(int prop, …).

Hay dos detalles que me gustarían comentar. El primero es respecto al código de arriba: es incorrecto. Al llamar a la función ‘foo’, cualquiera sea el valor que se pase como segundo parámetro, siempre se mostrará 0.0. Esto me tuvo de cabeza un rato cuando trataba de procesar la propiedad FPS, que es un float. Esto se debe a que va_args interpreta los floats como doubles. La forma correcta sería:

    float a = (float)va_arg(args, double);

Esto no pasa con los punteros a float (float*), ya que todos los punteros son realmente el mismo tipo de dato, sólo que el compilador los interpreta de una forma u otra únicamente al desreferenciar la dirección de memoria.

El segundo detalle es que no es posible utilizar parámetros variables en funciones virtuales. Me explico, se puede, pero una clase que quiera reimplementar dicho método no tendrá forma de pasarle los parámetros variables. Esto llevaba a que no podía hacer una jerarquía de clases que me permitiera implementar el control de las propiedades más comunes en clases generales y dejar las propiedades específicas de cada cámara en especializaciones progresivas. La forma más sencilla de resolver este problema es utilizar dos métodos, uno propio de la clase padre que extrae los parámetros variables, y otro virtual, posiblemente privado, que recibe un va_list y que es quien realmente los interpreta:

class A
{
public:
    void foo(int n, ...)
    {
        va_list args;
        va_start(args, n);
        _bar(n, args);
        va_end(args);
    }

protected:
    virtual void _bar(int n, va_list args) = 0;
};

class B : public A
{
protected:
    virtual void _bar(int n, va_list args)
    {
        // ...
    }
};

En este caso es importante saber que una vez procesados los parámetros variables, no es posible recuperarlos, por lo que hay que tenerlo en cuenta en caso de que los métodos deban ser llamados a lo largo de la jerarquía de clases.

¡Espero verlos la semana que viene con más anec-notas!