Estoy tratando de implementar el registro automático de clases en tiempo de ejecución (creo que esta técnica se conoce con el nombre de "patrón de registro"). En el siguiente ejemplo, estoy almacenando un int en un vector de miembro estático, pero el objetivo sería almacenar punteros de función que se puedan llamar más tarde.
Tengo entendido que, dado que el miembro registeredClasses en mi código a continuación se declara static, debería haber solo una instancia de él en todo el programa. Sin embargo, este parece no ser el caso:

Classregister.h

#ifndef CLASSREGISTER_H
#define CLASSREGISTER_H

#include <vector>
#include <iostream>

class ClassRegister
{
public:
    static std::vector<int> registeredClasses; //declaration

    ClassRegister(int id) {
        std::cerr << "registering " << id << " ";
        registeredClasses.push_back(id);
        std::cerr << "new size: " << registeredClasses.size() << "\n";
    }

    //code below just for testing
    ClassRegister() {}

    static int getSize() {
        return registeredClasses.size();
    }
};

#define REGISTER_CLASS(cls) \
    static ClassRegister myClass_##cls(1);

#endif // CLASSREGISTER_H

Classregister.cpp

#include "classregister.h"

std::vector<int> ClassRegister::registeredClasses; //definition

Class1.h (class2.h sigue el mismo patrón, omitido por brevedad)

#ifndef CLASS1_H
#define CLASS1_H

#include "classregister.h"

class Class1
{
public:
    Class1();
};

REGISTER_CLASS(Class1)

#endif // CLASS1_H

Class1.cpp (class2.cpp sigue el mismo patrón, omitido por brevedad)

#include "class1.h"

Class1::Class1()
{
}

Main.cpp

#include "classregister.h"
#include <iostream>

int main(int argc, char *argv[])
{
    std::cerr << "registering classes works...\n\n";

    std::cerr << "but reading from main() does not...\n";
    std::cerr << "checking size directly: " << ClassRegister::registeredClasses.size() << "\n";
    std::cerr << "checking size through function: " << ClassRegister::getSize() << "\n";
    std::cerr << "checking size on instance: " << ClassRegister().getSize() << "\n";
    std::cerr << "\n";

    std::cerr << "registration from main() uses a different register...\n";
    ClassRegister(2);
    ClassRegister(3);
    ClassRegister(4);
    std::cerr << "\n";

    std::cerr << "...which is not the one used in the header files\n";
    std::cerr << "checking size directly: " << ClassRegister::registeredClasses.size() << "\n";
    std::cerr << "checking size on instance: " << ClassRegister().getSize() << "\n";
    std::cerr << "checking size through function: " << ClassRegister::getSize() << "\n";
    std::cerr << "\n";
}

El problema que tengo es que solo funciona "a veces". Salida inicial:

registering 1 new size: 1
registering 1 new size: 2
registering classes works...

but reading from main() does not...
checking size directly: 0
checking size through function: 0
checking size on instance: 0

registration from main() uses a different register...
registering 2 new size: 1
registering 3 new size: 2
registering 4 new size: 3

...which is not the one used in the header files
checking size directly: 3
checking size on instance: 3
checking size through function: 3

Eliminé class1.cpp y class2.cpp, declarando los constructores en los archivos de encabezado, por ejemplo. Class1() {}. La inscripción no funcionó en absoluto:

//no output here
registering classes works...
//...

Luego revirtí el cambio y de repente obtengo el resultado esperado:

registering 1 new size: 1
registering 1 new size: 2
registering classes works...

but reading from main() does not... //it does work this time
checking size directly: 2
checking size through function: 2
checking size on instance: 2

registration from main() uses a different register...
registering 2 new size: 3
registering 3 new size: 4
registering 4 new size: 5

...which is not the one used in the header files
checking size directly: 5
checking size on instance: 5
checking size through function: 5

He estado investigando sobre este problema, pero no pude entender la causa. Supongo que tiene algo que ver con el orden de compilación (pero ¿cómo funciona el registro entonces?) O con enlace / duración del almacenamiento. ¿Alguien puede arrojar algo de luz sobre esto?

1
matpen 30 ene. 2016 a las 01:03

2 respuestas

La mejor respuesta

Creo que el truco que necesita aquí está en ClassRegister en lugar de un miembro de datos estáticos, use una función con un local estático. Esto a veces se denomina singleton, aunque no sé si ese es realmente el término correcto para ello.

El código probablemente se vería así:

typedef std::vector<int> register_type;
register_type& getRegister() {
    static register_type registeredClasses; //declaration
    return registeredClasses;
}
2
Zan Lynx 29 ene. 2016 a las 22:10

Cuando implemente un esquema de registro de clase automático, debe tener cuidado con el orden de inicialización estático, multihilo.

La razón por la que no funcionó cuando eliminó los archivos .cpp es porque los archivos de encabezado no se compilan y, por lo tanto, nadie sabe que hubo declaraciones.

0
Moved 29 ene. 2016 a las 22:10