C ++ design problem

Here's a description of my design problem. There is a class A (Singleton) that creates and maintains objects of class B. But there is a scenario where if a particular condition hits an object of class B, it must create another object of class B. But I need this to create an object that must be performed by the class A.

 

class B;
class A {

     private:
          A();
          class A *ptr;
     public:
         A & GetAInstance()
         {
           if (!ptr)
               ptr = new A;
           return ptr;
         }
         void CreateBInstances(std::string name)
         {
              map[name] = new B(name);
         }
};

Class B {
       public:
           B(std::string name) { }
       public:
           void checkCondition()
           {
                if  (condition == true)
                {
                   // So here the contidition is hit, I want to create
                   // another object of class B, but I want to intimate
                   // class A to do this job for me
                }
           }
};

      

I would like to understand and look for a better approach to this dirty work. Thanks in advance.

+2


source to share


3 answers


A :: GetAInstance must be static.

Then you can simply do the following



if (condition)
{ 
    A::GetAInstance().CreateBInstance(name);
}

      

+5


source


Much of the fluff around circular dependencies is confusing there when applied at implementation mechanisms rather than at the package or module level, and some of it stems from shortcomings in testing environments for Java. Due to Java's influence on mem, solutions are formulated in the idioms of this, not in C ++. It is not clear if you are trying to remove a specific circular dependency (code will not compile in C ++) or metaphysical objections.

One idiom for decoupling specific circular dependencies is in allocators in the C ++ standard library.

When you declare a list like this:

std::list < int, my_allocator < int > >

      

the allocator has a nested structure that allows access to the raw template for different specializations, so the implementation std::list

can allocate node objects, not just ints.

Assuming you have the following requirements:

  • the registry is global to the program (i.e. you don't need more than one registry for each object type, otherwise you need something like the factory pattern that SingleShot offers, although in C ++ you tend to use pattern more often than virtual polymorphism functions); so I prefer to use static factories rather than single ones.
  • class objects B

    should only be created by callingA::CreateInstance(name)

  • A

    acts like a registry, so repeated calls to instantiate with the same name return the same object
  • the code compiles correctly without specific circular references
  • can replace registry type or registered type for testing

This global registry does not require any knowledge of the types it creates, other than that they provide a constructor referencing const std :: string:



#include <string>
#include <map>

template < class T = int >
class Registry {
    static std::map<std::string,T*> map;

    public:

    static T& CreateInstance ( const std::string& name ) {
        typename std::map< std::string, T* >::iterator it = map.find ( name );

        if ( it == map.end() )
            return * ( map [ name ] = new T ( name ) );
        else
            return *it->second;
    }

    public:

    template < typename U > 
    struct rebind {
        typedef Registry<U> other;
    };
};

template  < class T >
std::map < std::string, T* > Registry<T>::map;

      

The corresponding registered object provides a private constructor and has a CreateInstance function as a friend:

template < typename R = class Registry<> >
class Registered {
        typedef typename R::template rebind< Registered < R > > ::other RR;

    private:
        friend Registered<R>& RR::CreateInstance ( const std::string& name );

        explicit Registered ( const std::string& name ) {
            // ...
        }

        Registered ( const Registered<R>& ) ; // no implementation

    public:
        void checkCondition()
        {
            bool condition = 7 > 5;

            if  ( condition )
            {
                RR::CreateInstance ( "whatever" );
            }
        }

    // ...
};

      

Because of the idiom, rebind::other

you don't need to write Registered<Registry<Registered<Registry ...

and avoid a specific circular dependency. Since the default is Registry<int>

never used other than to supply rebind

, it is not instantiated and therefore does not report an error that you cannot construct an int using new int ( name )

.

Then you can use types for your B and A:

typedef Registered<>  B;
typedef Registry<B>   A;

int main () {
    B& b1 = A::CreateInstance("one"); // create a B

    b1.checkCondition(); // uses A to create object internally

    B b2("two"); // compile error - can only create B using A

    return 0;
}

      

You can, of course, build Registered< MyMockRegistry >

for testing, another major objection to cyclically dependent types.

+2


source


Something doesn't smell so good all over the design, but it's hard to tell without more details. Anyway, you can do something like this, which avoids the circular dependency, but still B calls A:

class BRegistry
{
   virtual void CreateBInstances(std::string& name) = 0;
   ...
};

class A : public BRegistry
{
   public:

      virtual void CreateBInstances(std::string& name) {
         map[name] = new B(name, *this);
      }

      // Singleton stuff...

      ...
};

class B
{
   public:

      B (std::string& name, BRegistry& registry) {
         ...
      }

      void checkCondition()
      {
         if  (condition == true)
         {
            registry.CreateBInstances(name);
         }
      }

      ...
};

      

Basically, I extract the interface from A and use the interface B. A passes itself to B at creation time, but from point B it is an interface. There are no cycles.

Just a comment. 99% of the time I see a Singleton it is a misuse of the template. This is usually misused as just a convenience global variable, as shown by all the comments that jumped up to tell to use it internally.

+1


source







All Articles