0

I'm trying to implement user defaults in my C++ app. For that I created an interface class with one function in it:

class IRegisterUserDefaults
{
public:
    IRegisterUserDefaults();
    virtual void registerUserDefaults(){}
};

Each class inheriting from this interface class will implement the function to register the user defaults it needs to be set.

So far no problem. But what's the best way of calling it? I'm coming from Objective-C where I could just search through all classes and find the ones who implement the interface and call the registerUserDefaults function on them. I understand though that C++ doesn't have this level of introspection. It would be sufficient to call the function once per class (and thus make it static).

Objective

It would be great if the function would be called "automatically" if a class subclasses IRegisterUserDefaults. I tried calling the method from the IRegisterUserDefaults constructor but it looks like this doesn't call the subclass function properly. Is there a way to make this happen?

Also, what would be best way to make sure this is only called once per class?

guitarflow
  • 2,930
  • 25
  • 38
  • "Curious to hear your thoughts!" asking for opinions is offtopic, what is your question? – 463035818_is_not_an_ai Jun 05 '18 at 10:08
  • 2
    Why do you want this? My instinct is that this is not a good design choice for c++, but without knowing what problem you are trying to solve or what the registering of defaults is supposed to do it's hard to tell. Are the defaults run-time values? – super Jun 05 '18 at 10:19
  • @user463035818 I was wondering what the best solution might be. The optimal solution would call the appropriate function only by subclassing IRegisterUserDefaults. – guitarflow Jun 05 '18 at 10:47
  • @super My user defaults are something like the default font used for drawing text for example. This needs to be registered at some point. What I'm after is a convenience solution as I see a chance that one just forgets to call the registerUserDefaults method so I thought that it would be ideal if the method is called "automatically" through the superclass constructor. – guitarflow Jun 05 '18 at 10:51
  • Ok, since you paraphrased your problem, see these two links: https://isocpp.org/wiki/faq/strange-inheritance#calling-virtuals-from-ctors and https://isocpp.org/wiki/faq/strange-inheritance#calling-virtuals-from-ctor-idiom – andreee Jun 05 '18 at 11:01
  • How would your Objective-C program know if there were *instances* of subclasses of `IRegisterUserDefaults`? – Caleth Jun 05 '18 at 11:01
  • @guitarflow There is no "reflection" concept in C++ and indeed scanning the project for classes is very difficult if possible at all. Usually this requires some macro magic (or worse: some external C++ code generator) which most of the time leads to unmaintainable code. Perhaps C++ is not the correct tool for the job? – freakish Jun 05 '18 at 11:04
  • 1
    @Caleth It wouldn't, but there are ways to find out what (Objective-C) classes exist and instantiate those whose names (say) match some kind of pattern or which implement a particular method signature. – Paul Sanders Jun 05 '18 at 11:04
  • @freakish `Perhaps C++ is not the correct tool for the job`?? Sacrilege! – Paul Sanders Jun 05 '18 at 11:05

4 Answers4

2

IRegisterUserDefaults is not a meaningful interface, in any language.

It sounds like the actual problem you are trying to solve is "run some code once, at or near class first use". You can do that with something like this

class HasUserDefaults {
    static std::once_flag register_once;
    void registerUserDefaults() { /*...*/ }
public:
    HasUserDefaults ()
    { 
      // in all the constructors
        std::call_once(register_once, &HasUserDefaults::registerUserDefaults, this);
    }

    // other members
};
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • And there's no point making `registerUserDefaults()` `virtual` then, either. But my solution requires less boilerplate and lets you gather all the necessary initialisation in one place. – Paul Sanders Jun 05 '18 at 11:30
  • The *body* of `HasUserDefaults::registerUserDefaults` is going to be near the rest of `HasUserDefaults`, so I disagree that your solution "lets you gather all the necessary initialisation in one place" – Caleth Jun 05 '18 at 11:38
  • 1
    That's not how I read the OP's question. He wants to make sure that all derived classes 'check in' in some fashion, and to do that he needs to do something _with_ each one, as opposed to (ideally) _in_ each one. Hence my suggestion: he can just go `MakeDefaultsHandler (); MakeDefaultsHandler (); ...`, all in one place. The classes themselves will be implemented in whatever fashion the OP chooses, I'm not concerning myself with that. Anyway, let's stop speculating what he really wants, he needs to say. – Paul Sanders Jun 05 '18 at 11:43
  • Hmmm, a thought occurs. My template should be called `RegisterDefaultsHandler`. That better describes what it actually does. Edited my answer, it was useful talking it over. – Paul Sanders Jun 05 '18 at 11:44
  • Agreed that the interface does not make a lot of sense the way it is. As I said, I was hoping to implement it in a way that classes are registered automatically and that subclassing and implementing the method is enough. But as this doesn't work, your solution is probably the best one. There's no need for a "registry" of some sort and classes can handle the user defaults registration themselves. Thanks! – guitarflow Jun 07 '18 at 14:53
1

Does this work for you?

#include <iostream>
#include <string>

class IRegisterUserDefaults
{
public:
    IRegisterUserDefaults() {}
    virtual void registerUserDefaults() = 0;
};

class MoreDerivedRegisterUserDefaults : public IRegisterUserDefaults
{
public:
    MoreDerivedRegisterUserDefaults (int x, int y) : m_x (x), m_y (y) { }
    virtual void registerUserDefaults() override {
        std::cout << "MoreDerivedRegisterUserDefaults::registerUserDefaults called (" << m_x << ", " << m_y << ")" << std::endl;
    }
private:
    int m_x, m_y;
};

template <class T, typename... Args> void RegisterDefaultsHandler (Args... args) {
    T obj (args...);
    obj.registerUserDefaults ();
}    

int main ()
{
    RegisterDefaultsHandler<DerivedRegisterUserDefaults> ();
    RegisterDefaultsHandler<MoreDerivedRegisterUserDefaults> (1, 2);
    // ...
}

You have to instantiate each derived class somewhere.

Live demo (updated). Output:

DerivedRegisterUserDefaults::registerUserDefaults called
MoreDerivedRegisterUserDefaults::registerUserDefaults called (1, 2)

EDIT: After talking to @Caleth, I tweaked the code a little to make my intentions clearer.

EDIT 2: Variadiac template added, turned out to be easier than I thought, useful 'howto' guide here.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Good thought! This will be tough to implement though as the code's spread across several frameworks. There is not one central place like main() where all subclasses of IRegisterUserDefaults are known. Also, your code implies that all classes that subclass IRegisterUserDefaults have a default constructor without any arguments. That's not always the case. – guitarflow Jun 05 '18 at 12:19
  • Well `main()` (or whatever) only needs to see the relevant header files. And if you polish up your variadic template-fu you should be able to make one that can pass an arbitrary list of arguments to any particular constructor. Templates are good at that, I'm just not clever enough to post one for you. Vote me up, if you like. – Paul Sanders Jun 05 '18 at 12:25
  • TU for vote, added a variadic template for you since I wanted to learn how it's done. Turns out: trivial. – Paul Sanders Jun 05 '18 at 17:11
1

Do you have a single location where all those derived classes are known? In that case, do it there:

// The type-list can be used in many ways, as you need
using all_classes = std::tuple<A, B, C, D /* and so on */>;

template <class... Ts>
static void register_all_classes(Y<Ts...>*)
{ ((Ts().registerUserDefaults()), ...); }

register_all_classes((all_classes*)nullptr);

Otherwise, you must obviously go decentralized:

Do you have a single compilation-unit responsible for registering each class? In that case, use a namespace-scope object. Maybe use a helper for that:

template <class T>
struct Init {
    Init() { T().registerUserDefaults(); }
};

// Used in single TU as:
static Init<SomeRegisterUserDefaults> _;

Otherwise, take a look at std::ios_base::Init how <iostream> does it. I simplified because there was no need for uninit indicated:

template <class T>
struct MultiInit {
    MultiInit() { static Init<T> _; }
};

// Used in any number of TUs as:
static MultiInit<SomeRegisterUserDefaults> _;
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
0

Call the method in sub-class constructor, you cannot call this in base class constructor as the sub class is not yet constructed by then.

Jay KM
  • 21
  • 2