Pregunta Detectar modificación de variable en tiempo de ejecución en C / C ++


Estoy desarrollando una biblioteca en C ++ donde los usuarios / programadores 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.

Aquí hay un recorte, 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 Eso no inicializa algunos elementos de m_arr. Lo que me gustaría es crear una función en mi biblioteca que compruebe si initArray Inicializó todos los elementos de m_arr. Esta función debe ser llamada por una rutina de chequeo de cordura en tiempo de ejecución.

Mi pregunta: ¿es posible detectar cambios en esta matriz? Solo puedo pensar en inicializar la matriz con algunos valores no válidos (como NaN o Inf), llamada initArray y compruebe que todos los valores han cambiado.

Gracias por tus ideas,

David


Editar

Aquí hay un ejemplo del código de cliente que intento 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;
}

Así, olvidando el m_arr[1] este elemento no está inicializado y podría causar problemas en futuros cálculos. Eso es lo que me gustaría comprobar.


5
2018-03-09 11:10


origen


Respuestas:


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.

Alternativamente, 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 del operador sobrecargado [] para la asignación, mantenga un contador e incremente el contador para cada inicialización.

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

Espero que esto ayude.


3
2018-03-09 15:24



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


5
2018-03-09 11:14



Si está tratando de 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 completamente construido. Esto también te permitirá hacer m_arr privado:

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 a initArray, puede verificar que el vector que devuelve la clase derivada sea del tamaño correcto. O, si no necesita imponer el tamaño por adelantado, puede simplemente eliminar ese parámetro del constructor y aceptar cualquier longitud initArray devuelve como el tamaño previsto.)


3
2018-03-09 11:31



Aquí está mi sugerencia:

Agregue otro método virtual protegido a la clase base que llamó "InitArrayImpl" y ordene al creador de la subclase que complete la matriz en él.

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.


2
2018-03-09 11:21



Si la eficiencia no es uno de sus principales objetivos, puede

  • Bloquear el acceso directo a m_arr haciéndolo privado
  • agrega una matriz de bools del mismo tamaño que m_arr m_field_initialized = new bool[n] e inicializarlo con valores falsos.
  • Cree una instancia pública (o protegida) de la clase de acceso que se ajustará m_arr y m_field_initialized
  • El accesorio 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 verificación de inicialización, pruebe si todos los campos de m_field_initialized se establecieron en verdad

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

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

m_arr_initialized será establecido por su método de verificación de inicialización.


Si no es necesario que la matriz se asigne en la clase base, puede establecer m_arr a cero, deje la asignación para las subclases y simplemente verifique si m_arr el puntero se configuró como distinto de 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 asignación con valor inicial allocate(std::size_t size, double initVal) o incluso hacer cumplir la función de inicialización para ser pasado allocate(std::size_t size, double (*callback)(std::size_t element)), que se llamará en cada elemento. Hay muchas posibilidades


editar: después de su edición, sugiero un puntero (o referencia) al objeto de inicialización o a la devolución de llamada a una función en el constructor BaseClass. Esto obligará a las subclases a proporcionar el código de inicialización. Considera lo siguiente:

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

En tu clase base constructor

 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 alguna inicialización al 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 hacerlo const-correct


1
2018-03-09 13:32



Esto se parece menos a C ++ y más como C con un par de características útiles como constructores para mí. Su clase no puede decidir si es una clase en C o una clase en C ++ y creo que eso es parte del problema que está teniendo con la inicialización aquí.

¿Qué hay de hacer esto de una manera diferente, haciendo que el array sea privado y que proporcione una interfaz de inicialización no virtual que acepte un tipo de functor como std :: genera? Entonces llamas al funtor una vez para cada elemento. De esa manera, sabrá si llamaron o no su inicializador y sabrán que todos los elementos están inicializados de manera segura. No solo eso, sino que también están protegidos de las clases de niños, cambiándolas cuando quieran.

Si debe mantener su enfoque actual por alguna razón u otra, el uso de NaN o inf puede funcionar si puede garantizar que esos valores no detectarán una excepción en ningún hardware para el que planea lanzar la biblioteca. De manera más segura, simplemente elija un valor neutral como 0 o uno y, si el cliente no se inicia, simplemente deje en claro que se están disparando en el pie.

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


1
2018-03-09 15:20



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

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


1
2018-03-09 11:16



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

De todos modos, considera usar un std::vector<> (¿Por qué tu propia gestión de la memoria?) de boost::optional<double>. (Usted sabe sobre el impulsar las bibliotecas?)

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


0
2018-03-09 11:18



Creo que estás silbando al viento sobre 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 metes NaN en la matriz que luego verificas, todos los usuarios harán su inicialización 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.


0
2018-03-09 13:20