There is a class A and its descendants A1 , A2 , A3 , A4 , A5 , etc. There is also a class B , in which there must be a function F , which returns an array with names / pointers to A1 , A2 , A3 , A4 , A5 ...

An important rule: you cannot create objects of classes A1 , A2 , A3 , A4 , A5 before this function F is called from class B

Essentially (although this no longer concerns the task), in this function of class B will be the second function K , which will create objects by pointers from an array, returned to it from F

This is the task I met in practice. Google unfortunately does not understand me (or I him).

Note

.H

 TSubclassOf<YourClass> BlueprintVar; // YourClass is the base class that your blueprint uses 

.CPP

  ClassThatWillSpawnTheBlueprint::ClassThatWillSpawnTheBlueprint(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP) { static ConstructorHelpers::FObjectFinder<UBlueprint> PutNameHere(TEXT("Blueprint'/Path/To/Your/Blueprint/BP.BP'")); if (PutNameHere.Object) { BlueprintVar = (UClass*)PutNameHere.Object->GeneratedClass; } } 
  • one
    Out of the box in any way. C ++ does not support reflection. Make a list in manual or semi-automatic mode. - VladD
  • Semi-automatic mode is something like to call the classes A1, A2, A3, A4, A5 and then a cycle of type (A% n), n - Anton Ivanov
  • For example, so. Any solution will roll, there is no official language-supported solution. - VladD
  • I just need to shove about a few hundred pointers into an array and call objects randomly. It turns out that hardcore will have - Anton Ivanov
  • one
    @AntonIvanov, if you have so many classes and the difference in their functionality is not so great, then maybe it makes sense to think about a different architecture? As a rule, such a hardcode is completely unjustified and it is possible to find another solution. - alexis031182

3 answers 3

And, so you have a list of some files in some folder - and each of them has a class? It changes things.

Here code autogeneration by third-party tools can be useful. Take any interpreted programming language (although you can also write in C ++) - and write a program that retrieves a list of files from the specified directory and generates from them a .cpp file of the format you need. Then this file is added to the list of compilation units of the main program.

Perhaps here you will need to learn a way to handle at least some kind of project building system. Traditionally, C ++ uses make or its almost compatible analogue of nmake - but you can use something else. A good choice would be to use a build system that is already being used by your IDE. This will allow, for example, not to list the files in the folder - and get their list from the IDE - which removes the problems like "the file is excluded from the project - but it still affects the build result."

However, this is not necessary - you can get by with the usual batch file and / bash script.

When generating code, you need to understand two rules.

  1. Autogenerated bydlokod bydlokodom not considered. Seriously: when autogeneration works, nobody will be interested in looking into the generated file. Therefore, its human readability is not needed. The repetition of identical constructions is also permissible - this is what a person makes when copying errors, the computer does not allow such errors.

  2. When using version control systems, the auto-generated file, as well as any output and intermediate project files, is not included in the version control system. Instead, there must be a program in the version control system that allows the auto-generated file to be recreated.

    I still do not understand, after all, the problem, so I present a solution to what I understood.

    There is a certain class A and its descendants A1, A2, A3, A4, A5, etc.

     class A { public: A(std::string path): m_Path(path) { } virtual ~A() { } std::string path() const { return m_Path; } virtual std::unique_ptr<A> clone() = 0; private: std::string m_Path; }; class A1: public A { using A::A; public: std::unique_ptr<A> clone() override { return std::unique_ptr<A>{new A1{path()}}; } }; class A2 : public A { using A::A; public: std::unique_ptr<A> clone() override { return std::unique_ptr<A>{new A2{path()}}; } }; 

    There is also a class B, in which there must be a function F, which returns an array with names / pointers to A1, A2, A3, A4, A5 ...

     class B { public: std::vector<std::shared_ptr<A>> f() { std::vector<std::shared_ptr<A>> vec; vec.push_back(std::make_shared<A1>("first path")); vec.push_back(std::make_shared<A2>("second path")); } //.. }; 

    Essentially (although this no longer concerns the task), in this function of class B there will be the second function K, which will create objects using pointers from an array, returned to it from F.

     class B { public: //.. void k() { auto vec = f(); decltype(vec) anotherVec; for(auto item: vec) { anotherVec.push_back(item->clone()); } } }; 

    This is what I understood, if I understood wrong, then explain what you lack in the above code, let's try to figure it out.

      There is no means to do this automatically. But you can at least break down the list into elements and bring them somewhere closer to the class declarations. In this case, adding and deleting classes will become somewhat easier.

      Take the following class:

       class registry_entry { // поля и виртуальные функции, которые понадобятся классу B }; // Осторожно, не создавайте этот класс на стеке! template<T> class registry_entry<T> : registry_entry { public: registry_entry() { B::registerClass(typeid(T), this); } // а тут реализация виртуальных функций }; 

      Now, when implementing the A5 class, we register this class in the same compilation unit where its constructor is implemented:

       static registry_entry<A5> entry; A5::A5(...) { ... } 

      It remains to implement the method B::registerClass . When implementing it, you need to be careful - since the compiler does not guarantee the order of initialization of global and static variables, this method should not rely on the fact that the designers of global and static variables will have time to work. Probably a lazy initialization pattern is useful here.

      • Since the task of the TS needs to create instances of classes, it is possible to register a factory — that is, a pragmatic function that creates an instance of the class. Perhaps, here it will be necessary to “fill up” metaprogramming in some form. - VladD
      • If the format of the constructor is known in advance, then there is no reason to make metaprogramming, one abstract method in registry_entry enough. - Pavel Mayorov
      • In principle, if all constructors are syntactically the same, then the abstract method is not needed. registry_entry() { map[typeid(T)] = []() { return new T(); } } registry_entry() { map[typeid(T)] = []() { return new T(); } } . - VladD
      • Well, this is lambda, and with dynamic binding. Yes, this can be done - but it will not give any benefits. And without extra lambdas, the code is still read a little easier (elementary, less nesting levels). - Pavel Mayorov
      • Well, here you are without a lambda: registry_entry() { map[typeid(T)] = this; } Base* create() { return new T(); } registry_entry() { map[typeid(T)] = this; } Base* create() { return new T(); } registry_entry() { map[typeid(T)] = this; } Base* create() { return new T(); } . Although this is probably already an abstract method. // Actually, this is essentially your code. - VladD