2

Now, I implemented a factory class to dynamically create class with a idenification string, please see the following code:

void IOFactory::registerIO()
{
    Register("NDAM9020", []() -> IOBase * {
        return new NDAM9020();
    });

    Register("BK5120", []() -> IOBase * {
        return new BK5120();
    });
}

std::unique_ptr<IOBase> IOFactory::createIO(std::string ioDeviceName)
{
    std::unique_ptr<IOBase> io = createObject(ioDeviceName);
    return io;
}

So we can create the IO class with the registered name:

IOFactory ioFactory;
auto io = ioFactory.createIO("BK5120");

The problem with this method is if we add another IO component, we should add another register code in registerIO function and compile the whole project again. So I was wondering if I could dynamically register class from a configure file(see below) at runtime.

io_factory.conf
------------------
NDAM9020:NDAM9020
BK5120:BK5120
------------------

The first is identification name and the second is class name.

I have tried with Macros, but the parameter in Macros cann't be string. So I was wondering if there is some other ways. Thanks for advance.


Update:

I didn't expect so many comments and answers, Thank you all and sorry for replying late.

Our current OS is Ubuntu16.04 and we use the builtin compiler that is gcc/g++5.4.0, and we use CMake to manage the build.

And I should mention that it is not a must that I should register class at runtime period, it is also OK if there is a way to do this in compile period. What I want is just avoiding the recompiling when I want to register another class.

Phymin
  • 123
  • 8
  • 3
    The usual way is to move the `Register` statements you currently have, into the translation unit implementing each type (ie, `BK5120.cpp` or whatever). Then your factory doesn't accumulate knowledge of each concrete class. Then you just need to worry about static initialization rules ... – Useless Jun 08 '20 at 09:26
  • On what operating system or what target machine? Are you implementing an OS kernel or coding an embedded system? **Please [edit](https://stackoverflow.com/posts/62258839/edit) your question to improve it** (giving details such as your compiler and your OS) – Basile Starynkevitch Jun 08 '20 at 12:32
  • If you actually want to use run-time loadable components (modules, plugins), then settle for C and it will work. – Lorinczy Zsigmond Jun 08 '20 at 14:16
  • @BasileStarynkevitch I have update my question, Thanks! – Phymin Jun 09 '20 at 09:19
  • @Useless Thanks for your comment, but I actually don't understand your point exactly, could you give me more details? – Phymin Jun 09 '20 at 09:20
  • You have a statement where you call `Register`, one for each concrete type. If you leave them where they are, you have to keep editing the `registerIO` function every time you add a type. Instead, move the `Register` statement for each concrete type, into that type's implementation. Then each new type can register itself without any central function to edit. – Useless Jun 10 '20 at 18:24
  • @Useless Thanks for your reply, and I understand now. Do you have any suggestion how to implement this? – Phymin Jun 11 '20 at 08:51

4 Answers4

3

So I was wondering if I could dynamically register class from a configure file(see below) at runtime.

No. As of C++20, C++ has no reflection features allowing it. But you could do it at compile time by generating a simple C++ implementation file from your configuration file.

YSC
  • 38,212
  • 9
  • 96
  • 149
2

How to dynamically register class in a factory class at runtime period with c++

Read much more about C++, at least a good C++ programming book and see a good C++ reference website, and later n3337, the C++11 standard. Read also the documentation of your C++ compiler (perhaps GCC or Clang), and, if you have one, of your operating system. If plugins are possible in your OS, you can register a factory function at runtime (by referring to to that function after a plugin providing it has been loaded). For examples, the Mozilla firefox browser or recent GCC compilers (e.g. GCC 10 with plugins enabled), or the fish shell, are doing this.

So I was wondering if I could dynamically register class from a configure file(see below) at runtime.

Most C++ programs are running under an operating system, such as Linux. Some operating systems provide a plugin mechanism. For Linux, see dlopen(3), dlsym(3), dlclose(3), dladdr(3) and the C++ dlopen mini-howto. For Windows, dive into its documentation.

So, with a recent C++ implementation and some recent operating systems, y ou can register at runtime a factory class (using plugins), and you could find libraries (e.g. Qt or POCO) to help you.

However, in pure standard C++, the set of translation units is statically known and plugins do not exist. So the set of functions, lambda-expressions, or classes in a given program is finite and does not change with time.

In pure C++, the set of valid function pointers, or the set of valid possible values for a given std::function variable, is finite. Anything else is undefined behavior. In practice, many real-life C++ programs accept plugins thru their operating systems or JIT-compiling libraries.

You could of course consider using JIT-compiling libraries such as asmjit or libgccjit or LLVM. They are implementation specific, so your code won't be portable.

On Linux, a lot of Qt or GTKmm applications (e.g. KDE, and most web browsers, e.g. Konqueror, Chrome, or Firefox) are coded in C++ and do load plugins with factory functions. Check with strace(1) and ltrace(1).

The Trident web browser of MicroSoft is rumored to be coded in C++ and probably accepts plugins.

I have tried with Macros, but the parameter in Macros can't be string.

A macro parameter can be stringized. And you could play x-macros tricks.

What I want is just avoiding the recompiling when I want to register another class.

On Ubuntu, I recommend accepting plugins in your program or library

Use dlopen(3) with an absolute file path; the plugin would typically be passed as a program option (like RefPerSys does, or like GCC does) and dlopen-ed at program or library initialization time. Practically speaking, you can have lots of plugins (dozen of thousands, see manydl.c and check with pmap(1) or proc(5)). The dlsym(3)-ed C++ functions in your plugins should be declared extern "C" to disable name mangling.

A single C++ file plugin (in yourplugin.cc) can be compiled with g++ -Wall -O -g -fPIC -shared yourplugin.cc -o yourplugin.so and later you would dlopen "./yourplugin.so" or an absolute path (or configure suitably your $LD_LIBRARY_PATH -see ld.so(8)- and pass "yourplugin.so" to dlopen). Be also aware of Rpath.

Consider also (after upgrading your GCC to GCC 9 at least, perhaps by compiling it from its source code) using libgccjit (it is faster than generating temporary C++ code in some file and compiling that file into a temporary plugin).

For ease of debugging your loaded plugins, you might be interested by Ian Taylor's libbacktrace.

Notice that your program's global symbols (declared as extern "C") can be accessed by name by passing a nullptr file path to dlopen(3), then using dlsym(3) on the obtained handle. You want to pass -rdynamic -ldl when linking your program (or your shared library).

What I want is just avoiding the recompiling when I want to register another class.

You might registering classes in a different translation unit (a short one, presumably). You could take inspiration from RefPerSys multiple #include-s of its generated/rps-name.hh file. Then you would simply recompile a single *.cc file and relink your entire program or library. Notice that Qt plays similar tricks in its moc, and I recommend taking inspiration from it.

Read also J.Pitrat's book on Artificial Beings: the Conscience of a Conscious Machine ISBN which explains why a metaprogramming approach is useful. Study the source code of GCC (or of RefPerSys), use or take inspiration from SWIG, ANTLR, GNU bison (they all generate C++ code) when relevant

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 1
    That first paragraph does not answer the quoted question in any way at all, and the bolded introductory words come across as a bit patronising, do they not? – Asteroids With Wings Jun 08 '20 at 12:08
  • @Asteroids I did improve my answer, and gave examples. – Basile Starynkevitch Jun 08 '20 at 12:25
  • Wao! that's a lot of links! – YSC Jun 08 '20 at 13:18
  • Wow! from reading your answer, I find that I know just a little about c++. and I know the plugin pattern can solve my problem, but if I use this approach, I should do a big change of our current project. And I will try the approach you provided in the end of your answer. Thanks. – Phymin Jun 09 '20 at 09:25
  • I think you may misunderstand me about the Marcos part. What I mean is I can use a Macro like this ```REGISTER_CLASS(BK5120, IOBase, BK5120)``` but I cann't use it like this: ```REGISTER_CLASS("BK5120", IOBase, "BK5120")``` where the two strings are loaded from configure file. – Phymin Jun 09 '20 at 11:02
  • And the definition of the Macro is: ```#define REGISTER_CLASS(factoryname, basename, classname) \ Register(#factoryname, []() -> basename* { \ return new classname; \ }); ``` Sorry for the ugly format, but I don't know how to format it in comment. – Phymin Jun 09 '20 at 11:09
1

You seem to have asked for more dynamism than you actually need. You want to avoid the factory itself having to be aware of all of the classes registered in it.

Well, that's doable without going all the way runtime code generation!

There are several implementations of such a factory; but I am obviously biased in favor of my own: einpoklum's Factory class (gist.github.com)

simple example of use:

#include "Factory.h"
// we now have:
//
// template<typename Key, typename BaseClass, typename... ConstructionArgs>
// class Factory;
//
#include <string>

struct Foo { Foo(int x) { }; }
struct Bar : Foo { Bar(int x) : Foo(x) { }; }

int main()
{
    util::Factory<std::string, Foo, int> factory;
    factory.registerClass<Bar>("key_for_bar");
    auto* my_bar_ptr factory.produce("key_for_bar");
}

Notes:

  • The std::string is used as a key; you could have a factory with numeric values as keys instead, if you like.
  • All registered classes must be subclasses of the BaseClass value chosen for the factory. I believe you can change the factory to avoid that, but then you'll always be getting void *s from it.
  • You can wrap this in a singleton template to get a single, global, static-initialization-safe factory you can use from anywhere.

Now, if you load some plugin dynamically (see @BasileStarynkevitch's answer), you just need that plugin to expose an initialization function which makes registerClass() class calls on the factory; and call this initialization function right after loading the plugin. Or if you have a static-initialization safe singleton factory, you can make the registration calls in a static-block in your plugin shared library - but be careful with that, I'm not an expert on shared library loading.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • Sorry, but I still don't get it that what's the difference between your factory implementation and mine. Because my implementation can also register class like yours(IOFactory is also a subclass of a template Factory base class).How could I avoid recompiling if I want to register another class? – Phymin Jun 09 '20 at 10:55
  • @Phymin: My factory doesn't know about any classes. And its instantiation only knows about one base class, not all the others. So if you pass (a referene to) your factory to code in other translation units, they can register whatever classes they like. You could also make it a singleton, in its own translation unit (or just in a header if it's C++17) - and then you don't even have to pass it anywhere, and can just do: `Baz::getFactory().registerClass("key_for_quux");` – einpoklum Jun 09 '20 at 11:56
  • Yes, I understand that your factory class is easier to use than mine, but what I don't get is how to avoid recompiling if we need to register new classes. BTW, we use static library to link the whole project. – Phymin Jun 10 '20 at 00:27
0

Definetly YES!

Theres an old antique post from 2006 that solved my life for many years. The implementation runs arround having a centralized registry with a decentralized registration method that is expanded using a REGISTER_X macro, check it out:

https://web.archive.org/web/20100618122920/http://meat.net/2006/03/cpp-runtime-class-registration/

Have to admit that @einpoklum factory looks awesome also. I created a headeronly sample gist containing the code and a sample:

https://gist.github.com/h3r/5aa48ba37c374f03af25b9e5e0346a86

h3R
  • 11
  • 2
  • Hi, thanks for you reply, but I'm afraid this method can only available in shared linked libraries, but not in static linked libraries. – Phymin Jan 24 '22 at 13:28