views:

202

answers:

5

I need several C++ classes to have a static method "register", however the implementation of register varies between those classes.

It should be static because my idea is to "register" all those classes with Lua (only once of course).

Obviously I can't declare an interface with a static pure virtual function. What do you guys suggest me to do ? Simplicity is welcome, but I think some kind of template could work.

Example of what I would like to achieve

class registerInterface
{
public:
    static virtual void register() = 0; //obviously illegal
};

class someClass: public registerInterface
{
    static virtual void register()
    {
        //I register myself with Lua
    }
}

class someOtherClass: public registerInterface
{
    static virtual void register()
    {
        //I register myself with Lua in a different way

    }
}

int main()
{
    someClass::register();
    someOtherClass::register();

    return 0;
}
+6  A: 

Based on how you've described the problem, it's unclear to me why you even need the 'virtual static method' on the classes. This should be perfectly legal.

class SomeClass {
  static void register(void) {
    ...
  }
}

class SomeOtherClass {
  static void register(void) {
    ...
  }
}

int main(int argc, char* argv[]) {
  SomeClass::register();
  SomeOtherClass::register();

  return 0;
}

Drop the RegisterInterface, I don't think you need it.

Hitesh
I thought about that, but that's not very nice from a design perspective. Let's make it a bit more complex, and say that I would like to let a "lua register manager" to know about each type of class that should register with Lua, ( so in the main , I can do "for each registered class, Class::register(), using polymorphism )
Mr.Gando
@Mr.Gando: then choose a language that's nicer from a design perspective ;-). Classes are not first-class objects in C++, they don't exhibit polymorphism (an instance of someClass is an instance of registerInterface, but someClass itself doesn't have a type which is related to the type of registerInterface itself), and you can't really iterate over collections of them.
Steve Jessop
C++ classes are not first-class objects that you could put into a collection and iterate over. C++ is not as object oriented as say Ruby or Objective-C, where you could have a collection of class objects that you iterated over and invoked methods.
Hitesh
So there's no nicer way of doing it then ?
Mr.Gando
@Mr.Gando: I suspect not by your definition of "nice". But I'm still not clear what you're trying to achieve, so I can't offer you a C++ way of doing it for you to assess its niceness.
Steve Jessop
Oh, and since C++ lacks reflection there's no way to say "for each registered class" that automatically gives you a list of every class in the program that has `registerInterface` as a base.
Steve Jessop
Yeah, I understand that. My only concern is that I will keep adding classes that register themselves to Lua, and I will have to keep tracking them and manually registering them.
Mr.Gando
You can do this with global factory object instances, to have objects sort of self-register. It adds other potential issues, but nothing that can't be solved. I think it's worth the trouble... it's very nice to have the flexibility that self-registering classes gives you.
darron
Yeah I totally agree, that flexibility is really welcome :)
Mr.Gando
+2  A: 

If it helps, you could take Hitesh's answer, and add:

struct luaRegisterManager {
    template <typename T>
    void registrate() {
        T::registrate();
        // do something else to record the fact that we've registered - 
        // perhaps "registrate" should be returning some object to help with that
    }
};

Then:

int main() {
    luaRegisterManager lrm;
    lrm.registrate<someClass>();
    lrm.registrate<someOtherClass>();
}

More generally, if you want to introduce any dynamic polymorphism in C++, then you need an object, not just a class. So again, perhaps the various register functions should be returning objects, with some common interface base class registeredClass, or classRegistrationInfo, or something along those lines.

Could provide an example of what you feel it is that you need dynamic polymorphism for? Hitesh's code precisely matches your one example, as far as I can see, so that example must not cover all of your anticipated use cases. If you write the code that would be using it, perhaps it will become clear to you how to implement it, or perhaps someone can advise.

Something else that might help:

#include <iostream>
#include <string>
#include <vector>

struct Registered {
    virtual std::string name() = 0;
    virtual ~Registered() {}
    Registered() {
        all.push_back(this);
    }
    static std::vector<Registered*> all;
};

std::vector<Registered*> Registered::all;
typedef std::vector<Registered*>::iterator Iter;

template <typename T>
struct RegisteredT : Registered {
    std::string n;
    RegisteredT(const std::string &name) : n(name) { T::registrate(); }
    std::string name() { return n; }
    // other functions here could be implemented in terms of calls to static
    // functions of T.
};

struct someClass {
    static Registered *r;
    static void registrate() { std::cout << "registering someClass\n"; }
};
Registered *someClass::r = new RegisteredT<someClass>("someClass");

struct someOtherClass {
    static Registered *r;
    static void registrate() { std::cout << "registering someOtherClass\n"; }
};
Registered *someOtherClass::r = new RegisteredT<someOtherClass>("someOtherClass");

int main() {
    for (Iter it = Registered::all.begin(); it < Registered::all.end(); ++it) {
        std::cout << (*it)->name() << "\n";
    }
}

There are all sorts of problems with this code if you try to split it across multiple compilation units. Furthermore, this kind of thing leads to spurious reports from memory leak detectors unless you also write some code to tear everything down at the end, or use a vector of shared_ptr, Boost pointer vector, etc. But you see the general idea that a class can "register itself", and that you need an object to make virtual calls.

In C++ you usually try to avoid static initialisation, though, in favour of some sort of setup / dependency injection at the start of your program. So normally you would just list all the classes you care about (calling a function on each one) rather than try to do this automatically.

Steve Jessop
Ok, your answer is very good. So in the last example you statically registered those classes ( pre - main ) , and then iterated over the vector of registered Classes. I can see that this can actually lead to problems and should be avoided. Your add-on to Hitesh's answer is also very welcome and I think it is what I will use in the end.Thank you very much.
Mr.Gando
@Mr.Gando: I've now enhanced my static-registration code to reduce the amount of boilerplate used by each class to statically register itself. This doesn't affect the fundamental problems with static initialisation, of course. At the same time my compiler reminded me that we can't have a function called "register" - it's a keyword.
Steve Jessop
I do something like this all the time. I add a *registrar* class, which tracks registerables, though global instances of factory objects. The registrar is constructed upon first use (in a static Registrar::instance() function). By overloading 'new' and 'delete' in these classes to use a specific heap's set of functions (malloc(), free() wrapped in a specific DLL for simplicity), you can avoid problems doing this across DLL boundaries, etc. An 'unload' function will reduce the fake memory leak messages, but you'll still probably have falsely detected leaks from the global factory objects.
darron
I really liked your static-registration code. Is it really not recommended ? I mean, I think that could give me a lot of flexibility and minimize the risk of me/other developer forgetting to register some class X and then having unexpected behavior/bugs.
Mr.Gando
Globals are somewhat frowned on, but this isn't modified after setup, so not too bad. The problem is dynamic initialization of globals. See 3.6.2/3 in the spec: "It is implementation-defined whether or not the dynamic initialization of an object of namespace scope is done before the first statement of main. If [not], it shall occur before the first use of any function or object defined in the same translation unit as the object to be initialized". So to guarantee that `someClass` is registered if it's in a different TU, `main()` must explicitly use something from that TU.
Steve Jessop
Also, if the registry isn't in the same TU as the classes registering themselves, then you'll need to make sure that the vector has been constructed before anything tries to add itself to it. Something like a Meyers singleton. If your whole program is a single TU (or at least, `main()` and all the registered classes are in a single TU) then all these problems go away and initialization order is defined and occurs before `main()` is entered. But most programs aren't a single TU.
Steve Jessop
Finally, it's not very dependency-injection-ish for a list of registered classes to just magic itself up out of the ether. That could be a design concern, and applies equally in other languages if you use reflection to create such a list. These classes can't really be replaced with alternatives (for testing or as a configuration thing) if they're reached from some global that they've automatically put themselves in.
Steve Jessop
+1  A: 

Your intentions are noble, but your solution is inkling towards "overengineering" (unless I am missing an obvious solution).

Here is one possibility: You can use the Virtual Friend function idiom For example,

class RegisterInterface{
   friend void register(RegisterInterface* x){x->do_real_register();}
 protected:
   virtual void do_real_register();
}

class Foo : public RegisterInterface{
 protected:
  virtual void do_real_register(){}
};

class Bar : public RegisterInterface{
 protected:
  virtual void do_real_register(){}
};

int main(int argc, char* argv[]) {
  BOOST_FOREACH(RegisterInterface* ri, registered_interfaces)
  {
    register(ri);
  }
  return 0;
}
imran.fanaswala
A: 

How about this way? Define an interface class:

// IFoobar.h
class IFoobar{
    public:
        virtual void Register(void) = 0;
}

Then define the class that handles the register..

// RegisterFoobar.h
class RegisterFoobar{
    public:
        // Constructors etc...
        IFoobar* fooBar;
        static void RegisterFoobar(IFoobar&  fubar){
             foobar = &fubar;
        }
    private:
        void Raise(void){ foobar->Register(); }
}

Now, then define another class like this

// MyFuBar.h
class MyFuBar : IFoobar{
    public:
        // Constructors etc...
        void Register(void);
    private:
        RegisterFoobar* _regFoobar;
}

Call the code like this:

//MyFuBar.cpp
MyFuBar::MyFuBar(){
    _regFoobar = new Foobar();
    _regFoobar->RegisterFoobar(this);
}
void MyFuBar::Register(void){
    // Raised here...
}

Maybe I have misunderstood your requirements...

tommieb75
+1  A: 

I know you've already accepted an answer, but I figured I would write this up anyway. You can have self-registering classes if you use some static initialization and the CRTP:

#include <vector>
#include <iostream>

using namespace std;

class RegisterableRoot // Holds the list of functions to call, doesn't actually need
                       // need to be a class, could just be a collection of globals
{
  public:
  typedef void (*registration_func)();
  protected:
  static std::vector<registration_func> s_registery;
  public:
  static void do_registration()
  {
    for(int i = 0; i < s_registery.size(); ++i)
      s_registery[i]();
  }
  static bool add_func(registration_func func) // returns something so we can use it in
                                               // in an initializer
  {
     s_registery.push_back(func);
     return true;
  }
};



template<typename RegisterableType>          // Doesn't really need to inherit from
class Registerable : public RegisterableRoot // RegisterableRoot
{
   protected:
   static const bool s_effect;
};


class A : public Registerable<A> // Honestly, neither does A need to inherit from 
                                 // Registerable<T>
{
   public:
   static void Register()
   {
     cout << "A" << endl;
   }
};

class B : public Registerable<B>
{
   public:
   static void Register()
   {
     cout << "B" << endl;
   }
};

int main()
{

  RegisterableRoot::do_registration();
  return 0;
}


std::vector<RegisterableRoot::registration_func> RegisterableRoot::s_registery;

template <typename RegisterableType> // This is the "cute" part, we initialize the 
                                     // static s_effect so we build the list "magically"
const bool Registerable<RegisterableType>::s_effect = add_func(&RegisterableType::Register);

template class Registerable<A>; // Explicitly instantiate the template
                                // causes the equivalent of
                                // s_registery.push_back(&A::Register) to
                                // be executed
template class Registerable<B>;

This outputs

 A
 B

although I wouldn't rely on this order if I were you. Note that the template class Registerable<X> need not be in the same translation unit as the call to do_registration, you can put it with the rest of your definition of Foo. If you inherit from Registerable<> and you don't write a static void Register() function for your class you'll get a (admittedly probably cryptic) compiler error much like you might expect if there really was such a thing as "static virtuals". The "magic" merely adds the class specific function to the list to be called, this avoids several of the pitfalls of doing the actual registration in a static initializer. You still have to call do_registration for anything to happen.

Logan Capaldo
That's actually very nice. :) , I'm not very proficient with C++ Templates, could you comment a bit that sample code ? Thanks!
Mr.Gando
@Mr.Gando I've added some comments and in doing so realized a bunch of the code was superfluous. I've left it as is though mentioning the unnecessary stuff in the comments. Hope this helps.
Logan Capaldo
Sure it does, thank you Logan. This question did become very educational for me!
Mr.Gando