Thread: Self-registering Components

  1. #1
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895

    Self-registering Components [partially resolved]

    Hi,

    I've run into the following problem: I have a very simple base class:

    Code:
    class Base
    {
    public:
      virtual const char *name() = 0;
      virtual void whatever() = 0;
      // ...
    };
    And a few derived classes, each one having exactly one instance:

    Code:
    class D1
    {
    public:
      virtual const char *name() {
        return "d1";
      }
    };
    
    D1 the_d1;
    
    class D2
    {
    public:
      virtual const char *name() {
        return "d2";
      }
    };
    
    D2 the_d2;
    And a std::map:

    Code:
    std::map<std::string, Base *> objects;
    That gets filled and queried with these:

    Code:
    void add_object(Base &b) {
      objects.insert(b.name(), &b);
    }
    Base *get_object(const char *name) {
      iterator it = objects.find(name);
      if(it != objects.end()) {
        return it->second;
      } else {
        return 0;
      }
    }
    OK. Now I want these global objects to register themselves in the map. Easy. Just do so in the constructors:

    Code:
    D1::D1() {
      add_object(*this);
    }
    D2::D2() {
      add_object(*this);
    }
    Only, I don't want to repeat the code that often. So the obvious idea is to put the code into the base constructor:

    Code:
    Base::Base() {
      add_object(*this);
    }
    Only, this doesn't work. I can't call a virtual function from the base constructor because the vtable is not yet initialized.

    So, I'm basically looking for a workaround for that. I want my global objects to register themselves in the map without having to put code in each and every constructor. Is this possible?


    Edit: OK, now I wrote a wrapper to do the registering and that works. I wonder if there's a more elegant way...
    Last edited by CornedBee; 01-14-2005 at 10:11 AM. Reason: Resolved
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  2. #2
    Registered User
    Join Date
    Aug 2003
    Posts
    470
    You might be able to get by with something like

    Code:
    class B {
    public:
             static B* create() {
                     B* b = new B();
    
                    // use b to get dynamic dispatch
                    return b;
             }
    private:
             B() { }
    };

  3. #3
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Nope, that doesn't help me. But thanks for the suggestion.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  4. #4
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    OK, now I wrote a wrapper to do the registering and that works.
    Out of curiosity, what does this 'wrapper' do? I've run into similar problems before, and just ended up retyping the code in each of the constructors, so it'd be really nice to know a way around that
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  5. #5
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    you could do something like this:

    Code:
    struct Base {
     const char * name() {
      return _name;
      }
     Base(const char * set)
     : _name(set) {
      add_object(*this);
      } 
     protected:  
     const char * _name;
     };
    then of course:

    Code:
    D1::D1() 
     : Base("d1") {
      
     }
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  6. #6
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Nice suggestion, Sebastiani. Very nice.

    Hunter2: It works like this:
    Code:
    template <typename DER>
    class wrapper
    {
      DER obj;
    
    public:
      wrapper() {
        add_object(obj);
      }
    };
    Since I never directly access the global object, it doesn't matter whether it's a
    D1 the_d1;
    or a
    wrapper<D1> the_d1;

    The effect is the same (it actually offers additional security), and the object is fully constructed when I call the virtual function.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  7. #7
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    Ah, I see. And I imagine there's a convenient operator DER() defined.

    Truly, I learn more interesting things from lurking than from coding
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  8. #8
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Quote Originally Posted by Hunter2
    Ah, I see. And I imagine there's a convenient operator DER() defined.
    No, there isn't. add_object stores a pointer to the private member of the holder in a map and the object is never accessed except through the map.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  9. #9
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    So the objects are created using new, and they add themselves to the map without you ever accessing them directly. Does that mean the syntax goes something like:
    Code:
    new D1;
    new D2;
    If so, it seems to me like you might as well have taken the call to add_object() out of the constructor and just done this:
    Code:
    add_object(*(new D1));
    add_object(*(new D2));
    They're fully constructed when being added this way too. Better yet, make add_object() take a Base* rather than Base&, and you won't have to dereference the pointer returned by new.
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  10. #10
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    They're not constructed with new. They're global variables.

    See, the idea I have is a very limited from of something component-object-model-like. I'm basically trying to write a library that gives an object-oriented approach to system-specific features. One of these features is the Shell, as understood in Win32. The shell executes apps while looking them up in the path (system()), executes data files through associated apps (ShellExecute) and has its own directory hierarchy (e.g. the shell namespace in Win32, which has the Desktop as its root.)

    The system class has a method get_shell, which returns a pointer to a shell object. shell is mostly an interface, though, and various concrete shells can be requested by name. So, for example, a Linux application can request a Gnome shell, a KDE shell, or the simple Console fallback shell.

    Since not every shell is available everywhere, these things should be optionally built in. For example, no one would like me if I required the Qt libraries on a Gnome system just because I give the option to use a KDE shell. This means the entire KDE shell is supposed to be separated into its own module and optionally linked in.
    How to request the shell then, though? I needed a system where linked-in object files registered their shells with the common code automatically. Otherwise I'd need a special initialization function, and I really want to avoid this need.
    My idea was that every module should have exactly one global object of each of the shell classes it defines, and that they should register themselves in the global map. And I managed to do it. I even managed to avoid various traps in initialization order between modules.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  11. #11
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    Since we're on the topic of globals, initialization, and libraries that may or may not be linked in - I thought that this thread from comp.lang.c++.moderated would be informative: Safe to use global for automated initialization?

    gg

  12. #12
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Thank you, Codeplug. That was indeed highly informative - and disturbing, because that means my design won't work.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  13. #13
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    I think that all your design needs to be correct, is to make objects a static within an accessor function:
    Code:
    typedef std::map<std::string, Base *> object_map;
    
    object_map& get_object_map() {
      static object_map objects;
      return objects;
    }
    
    void add_object(Base &b) {
      get_object_map().insert(std::make_pair(b.name(), &b));
    }
    
    Base *get_object(const char *name) {
      object_map &objects = get_object_map();
      object_map::iterator it = objects.find(name);
      if(it != objects.end()) {
        return it->second;
      } else {
        return 0;
      }
    }
    Because global object constructors in other translation units will reference add_object(), those globals will be linked in (even if you use /OPT:REF in MSVC). If your global objects have any dependencies in terms of intialization order, then you have problems

    gg

  14. #14
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    The object map isn't the problem. The problem lies in the inclusion of the code that contains the shells - the way I have it, they'll get ignored if they're part of a static library, because they never get directly referenced from without.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  15. #15
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    >> The object map isn't the problem.
    It's a problem, maybe not the problem You have to make objects a static of a function, otherwise you have no gaurentee that it will be initialized when global constructors reference it.

    >> ...they never get directly referenced from without.
    That doesn't matter as long as they reference something from within, like add_object(). The linker has to consider references in both directions.

    gg

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. win32 GUI components
    By abraham2119 in forum Windows Programming
    Replies: 5
    Last Post: 06-18-2009, 10:18 AM
  2. Sharing Components Between MSIs
    By mercury529 in forum Windows Programming
    Replies: 0
    Last Post: 12-06-2006, 04:01 PM
  3. Question about registering your window
    By PJYelton in forum Windows Programming
    Replies: 7
    Last Post: 03-18-2003, 02:26 PM