2010-03-09 11 views
5

Estoy desarrollando una biblioteca en C++ donde los usuarios/programador extenderán una clase BaseClass que tiene un método initArray. Este método debe ser implementado por el usuario/programador y normalmente debe inicializar todos los elementos de la matriz m_arr.Detecta modificación de variable en tiempo de ejecución en C/C++

Aquí es una snipplet, modificado para este ejemplo:

class BaseClass { 
    public: 

    BaseClass(int n) { 
     m_arr = new double[n]; 
     size = n; 
    }; 
    virtual ~BaseClass(); 

    int size; 
    double* m_arr; 

    virtual int initArray(); 
}; 

A veces, el usuario/programador implementa un initArray que no inicializa algunos elementos de m_arr. Lo que me gustaría es crear una función en mi biblioteca que compruebe si initArray hizo inicializar todos los elementos de m_arr. Esta función debe invocarse mediante rutinas de verificación de cordura en runtime.

Mi pregunta: ¿es posible detectar cambios en esta matriz? Solo puedo pensar en inicializar la matriz con algunos valores inválidos (como NaN o Inf), llamar al initArray y verificar que todos los valores hayan cambiado.

Gracias por sus ideas,

David


Editar

Aquí está un ejemplo del código de cliente que estoy tratando de detectar: ​​

// .h: 
class MyExample : public BaseClass { 
    public: 
    MyExample(); 
    virtual ~MyExample(); 
    virtual int initArray(); 
}; 

// .cpp: 
MyExample::MyExample() : BaseClass(3) { } 
MyExample::~MyExample() { } 
int MyExample::initArray() { 
    m_arr[0] = 10; 
    //m_arr[1] = 11; // let's say someone forgot this line 
    m_arr[2] = 12; 
    return 0; 
} 

Entonces, al olvidar el m_arr[1] este elemento no se inicializa y podría causar problemas en cálculos futuros. Eso es lo que me gustaría comprobar.

+6

Claro, haga que m_arr sea privado y proporcione accesodores. –

Respuesta

3

No veo una manera directa de hacer esto en C++. Lo que intenta implementar es filtros en Ruby on Rails donde antes de acceder a cualquier método, se invocan los filtros.

Como alternativa, puede envolver su matriz dentro de una estructura y dentro de esta estructura sobrecargar al operador [] tanto para la asignación como para el acceso.

Ahora:

1) Dentro de la operador sobrecargado [] para la asignación, mantener un contador y incrementar el contador para cada inicialización.

2) Dentro del operador de acceso [] sobrecargado, compruebe el contador antes de acceder al contenido de la matriz si es igual al número total de elementos. Si no, lanzar error.

Espero que esto ayude.

+0

Me gusta esta respuesta porque se mantiene con la estructura propuesta por el ejemplo. – YuppieNetworking

+0

m_arr [0] = 10; m_arr [0] = 11; // ¡Uy, copiar y pegar el error! m_arr [2] = 12; –

+0

@Mark gracias por señalar eso. Puede agregar una condición antes de incrementar el contador para verificar si el elemento actual no está inicializado – mukeshkumar

5

¿Por qué no utilizar un std :: vector? El usuario final se agregaría usando push_back, y puede verificar el tamaño para ver cuántos elementos se agregaron.

+0

Porque estoy manteniendo este código y usa mucho la estructura de matrices para acoplar la biblioteca con algunas bibliotecas escritas en C. – YuppieNetworking

+0

@YuppieNetworking Puede usar vectores con código C. –

+0

@Neil Butterworth Tenga en cuenta que std :: vector se reasigna en algunos de los cambios de tamaño y también se pueden cambiar las ubicaciones de memoria de los elementos. Esto a veces es crítico. – doc

1

Su idea de usar inf o NaN funcionaría. Cualquier inicializador predeterminado funcionaría lo suficientemente bien.

Otra idea es crear una función de miembro de acceso para modificar elementos. En el descriptor de acceso, configuraría el indicador modificado, en InitArray() borraría el indicador modificado.

+1

NaNs de ingenio de idea funcionarán hasta que la subclase no esté inicializando la matriz con NaN por sí mismo. Desafortunadamente, esto puede ser parte de la funcionalidad de la subclase. Solo imagine que la subclase toma dos parámetros 'a, b' y el valor inicial es' a/b'. El NaN resultante (por ejemplo, en el caso de a = 0 yb = 0) es una parte de su especificación. O, por otro lado, puede que la inicialización sea tan compleja que involuntariamente pueda devolver NaN debido a errores redondos. Posibles errores que pueden ocurrir con dicho enfoque pueden ser muy difíciles de rastrear. – doc

+0

@doc: gran punto – YuppieNetworking

0

Esto depende de lo que está tratando de hacer: ¿realmente necesita crear la matriz en la clase base, o puede usar la sugerencia de Neil de push_back()?

De todos modos, considere utilizar un std::vector<> (¿por qué hacer su propia gestión de memoria?) De boost::optional<double>. (¿Usted sabe sobre el boost libraries?)

Además, ¿realmente desea dividir la creación del objeto en dos fases? Cualquier código de cliente que crea un descendiente BaseClass necesita llamar al initArray(). ¿Te preocupa la eficiencia?

+0

Este diseño se debe a que BaseClass es en realidad un objeto con estados (como en una simulación). initArray inicializa el estado del objeto y se puede inicializar varias veces a medida que cambian los estados. – YuppieNetworking

+0

'boost :: optional' es genial, pero no se puede exponer de forma segura al código-C. –

2

Aquí está mi sugerencia:

Agregar otro método virtual protegida a la clase base que llama "InitArrayImpl" y ordenar el creador de la subclase para llenar la matriz en ella.

El método initArray debe ser público no virtul, en este método, realizar una llamada a InitArrayImpl, antes de la llamada, inicializar la matriz con valores no válidos, después de la llamada, probar si se cambiaron todos los valores.

El cliente de la clase debe estar expuesto solo al método initArray.

+0

void Base :: initArray() {initArrayWithInvalidValue(); initArrayImpl(); checkArray(); } donde initArrayImpl() es una función virtual. – Corwin

+0

Sí, mis pensamientos iniciales fueron en esta dirección. Es por eso que les pregunté a ustedes, ya que probablemente podría hacerse de otra manera. – YuppieNetworking

+0

Esa es la forma idiomática. De hecho, si escuchas el consejo de Herb Sutter (y es como si supiera algo, ¿no?) Las interfaces nunca deberían exponer los métodos virtuales. Los métodos no virtuales que invocan métodos virtuales privados permiten verificar las condiciones previas y posteriores en la clase base. –

3

Si está intentando hacer una interfaz "infalible", en lugar de pensar en initArray() como una rutina con efectos secundarios ... ¿por qué no estilizarlo como algo más funcional? initArray() podría tener la responsabilidad de asignar y construir un vector, y devolver una referencia a ese objeto totalmente construido. Esto también permitirá hacer m_arr privada:

class BaseClass { 
private: 
    size_t size; 
    auto_ptr< vector<double> > m_arr; 

public: 
    BaseClass(size_t n) { 
     size = n; 
    }; 
    virtual ~BaseClass(); 

protected: 
    virtual auto_ptr< vector<double> > initArray() const; 
}; 

(Nota: Después de llamar initArray puede verificar que el vector de la clase derivada pasa de nuevo es el tamaño adecuado o si no es necesario para hacer cumplir. el tamaño por adelantado, usted puede simplemente eliminar ese parámetro del constructor y aceptar la longitud que initArray devuelva como el tamaño deseado.)

+0

Estoy de acuerdo con su enfoque funcional. Creo que iré de esta manera ya que me gusta la rutina sin efectos secundarios. Sin embargo, como le dije a Neil, tengo muchos usuarios/programadores que ya codifican con initArray y sus efectos secundarios, así que esperaba mejorar algunos detalles sobre esta biblioteca con un bajo impacto en sus hábitos. – YuppieNetworking

0

Creo que está silbando en el viento en esto. No puede dictar que todos los valores estén debidamente completados. En el mejor de los casos, todo lo que puede hacer es dictar que el usuario introduzca algún valor en los elementos de la matriz. Si rellena NaNs en la matriz que luego verifica con cordura, todos los usuarios lo harán inicializar con cero, -1 o MAX_DOUBLE. Así que es mejor que les proporciones un código para hacerlo y termines con eso.

Mi clase derivada puede usar la clase base para reservar 1000 elementos, pero puede contener su propio recuento de cuántos elementos ha utilizado realmente.

1

Si la eficiencia no es uno de sus principales objetivos, es posible que

  • Bloquear el acceso directo a m_arr por lo que es privado
  • añadir variedad de Bools del mismo tamaño que m_arr m_field_initialized = new bool[n] e inicializar con valores falsos
  • Crear instancia pública (o protegido) de la clase de acceso, que envolverá m_arr y m_field_initialized
  • Accessor debe tener setVal(int i, double val) método que establecerá m_arr[i] y adicionalmente m_field_initialized[i] bandera para true;
  • En su método de comprobación de inicialización pruebe si todos los campos de m_field_initialized se establecieron en verdadero.

Puede mejorar esto proporcionando un método para un acceso directo más rápido, cuando se cumplen las condiciones de inicialización. Devolverá el puntero nulo antes de la verificación de inicialización, y después de que la inicialización fue exitosa, devolverá su puntero de matriz.

double * directAccess() { 
    if (m_arr_initialized) return m_arr; 
    else return 0; 
} 

m_arr_initialized se fijará por el método de comprobación de inicialización.


Si no se requiere para la matriz que se asignará en la clase base puede configurar m_arr a cero, deje de asignación para las subclases y simplemente comprobar si m_arr puntero se establece en no cero. Adicionalmente se puede establecer un campo válido que denote el tamaño asignado.O puede bloquear previamente el acceso a m_arr y proporcionar el método en la clase base para la asignación allocate(std::size_t size), o la asignación con el valor inicial allocate(std::size_t size, double initVal) o incluso hacer cumplir la función de inicialización que se aprobará allocate(std::size_t size, double (*callback)(std::size_t element)), que se invocará en cada elemento. Hay muchas posibilidades


edición: después de la edición que sugieren un puntero (o de referencia) a cualquiera de los objetos de inicialización o de devolución de llamada a una función en BaseClass constructor. Esto obligará a las subclases a proporcionar el código de inicialización. Considere lo siguiente:

class InitializerInterface 
{ 
    public: 
    virtual double get(int element) const = 0; 
} 

En su base de constructor de la clase

BaseClass(int n, const InitializerInterface & initializer) { 
    m_arr = new double[n]; 
    size = n; 
    for (int i = 0; i < n; i++) 
     m_arr[i] = initializer.get(i); 
}; 

Ahora cualquier subclase debe pasar un poco de inicialización de constructor. Por supuesto, puede reemplazar el método get() por un functor o agregar soporte para la función de devolución de llamada. Depende de tus preferencias

// última edición para que sea const-corrección

1

Esto se ve menos como C++ y más como C con un par de características útiles como constructores a mí. Tu clase no puede decidirse si se trata de una clase C-struct o C++, y creo que eso es parte del problema que estás teniendo con la inicialización aquí.

¿Qué tal hacer esto de otra manera, hacer que la matriz sea privada y proporcionar una interfaz de inicialización no virtual que acepte un tipo de functor como std :: generate. A continuación, llame al functor una vez para cada elemento. De esa forma sabrá si llamaron o no a su inicializador y saben que todos los elementos se inicializaron de manera segura. No solo eso, entonces están protegidos de las clases para niños, cambiándolos cuando lo deseen.

Si necesita mantener su enfoque actual por alguna razón u otra, el uso de NaN o inf podría funcionar si puede garantizar que esos valores no atraparán una excepción en el hardware para el que planea lanzar la biblioteca. De forma más segura, simplemente elija un valor neutral como 0 o uno y si el cliente no se inicializa, simplemente deje perfectamente claro que se están disparando a sí mismos en el pie.

En otras palabras, que los datos sean públicos Y exigir que se inicialice son dos objetivos (casi) mutuamente excluyentes.

+0

Parece un par de características C + porque el código está muy recortado. Estoy de acuerdo con su punto NaN. – YuppieNetworking

0

También puede establecer un punto de interrupción de datos a través de registros de depuración. Este link tiene algunas informaciones sobre el tema.

+0

Gracias, pero estoy buscando una solución de tiempo de ejecución, sin depuradores. – YuppieNetworking

Cuestiones relacionadas