views:

556

answers:

6

Hello,

I am trying to write a C++ implementation of factory design pattern. I would also like to do it using shared objects and dynamic loading. I am implementing a function called new_animal() which is passed a string. If the string is "dog", then it needs to see if a class dog is registered in shared object and create a dog object. If the string is "cat" it needs to find registered class cat and return an object of it. The function new_animal() does not know ahead of the time what strings will be passed to it. Thus, it would error out if a string with corresponding unregistered class is passed. Here is the code -

creator.hpp -

#ifndef CREATOR_HPP
#define CREATOR_HPP

#include <string>

class Animal {

     public :
     virtual string operator() (const string &animal_name) const = 0;
     virtual void eat() const = 0;
     virtual ~Animal() { }
};

class AnimalCreator {
     public :
     // virtual Animal *create() const = 0;
     virtual ~AnimalCreator() { }
};

typedef Animal* create_animal_t();
typedef void destroy_animal_t(Animal *);
#endif

cat.hpp -

#ifndef CAT_HPP
#define CAT_HPP

#include "creator.hpp"
#include <iostream>
#include <string>

class cat : public Animal {

     public :
          string operator() (const string &animal_name) const { return "In cat () operator"; }
          void eat() const { cout << "cat is eating" << endl; }
};

class catCreator : public AnimalCreator {
     public :

}theCatCreator;

#endif

cat.cpp -

#include "cat.hpp"
#include <iostream>

using namespace std;

extern "C" Animal *create() {
     cout << "Creating cat ..." << endl;
     return new cat;
}

extern "C" void destroy(Animal* a) {
     delete a;
}

dog.hpp -

#ifndef DOG_HPP
#define DOG_HPP

#include <string>
#include "creator.hpp"

class dog : public Animal {
     public:
     string operator() (const string &animal_name) const { return "In dog"; }
     void eat() const { cout << "Dog is eating" << endl; }
};

class dogCreator : public AnimalCreator {
     public:
}theDogCreator;

#endif

dog.cpp -

#include "dog.hpp"
#include <iostream>

using namespace std;

extern "C" Animal *create() {
     cout << "Creating dog" << endl;
     return new dog;
}

extern "C" void destroy(Animal *aa) {
     delete aa;
}

main.cpp - 

#include "creator.hpp"
#include "cat.hpp"
#include "dog.hpp"

#include <iostream>
#include <string>
#include <map>
#include <dlfcn.h>

map<string, AnimalCreator *> AnimalMap;

void initialize() {
     AnimalMap["dog"] = &theDogCreator;
     AnimalMap["cat"] = &theCatCreator;
}

Animal * new_animal(const string &animal) {
     static bool isInitialised (false);
     if (!isInitialised) {
          initialize();
          isInitialised = true;
     }

     AnimalCreator *theAnimalCreator = AnimalMap[animal];
     if (!theAnimalCreator) {
          cout << "error: " << animal << " not registerd" << endl;
          exit(1);
     }

     Animal *theAnimal = theAnimalCreator->create();
     return theAnimal;     
}

int main() {

     void *animal = dlopen("animal", RTLD_LAZY);
     if (!animal) {
       cout << "error is dlopen" << endl;
          exit(1);
     }

     create_animal_t* new_animal = (create_animal_t*) dlsym(animal, "create");
      if (!new_animal) {
          cout << "error is dlsym create" << endl;
          exit(1);
     }
      destroy_animal_t* destroy_animal = (destroy_animal_t*) dlsym(animal, "destroy");
      if (!destroy_animal) {
          cout << "error is dlsym destroy" << endl;
          exit(1);
     }

     Animal *a = new_animal("dog");
     Animal *b = new_animal("cat");
     a->eat();
     b->eat();

     destroy_animal(a);
     destroy_animal(b);

     dlclose(animal);
     return 0;
}

Makefile -

# macros
CC = g++
CFLAGS = -g -Wall
MODFLAGS = -fpic -shared
LDFLAGS = -ldl
OBJECTS = main.o animal

# targets
all:      foo
foo:      $(OBJECTS)
          $(CC) -o foo $(OBJECTS) $(LDFLAGS)
animal:   dog.cpp cat.cpp
          $(CC) $(CFLAGS) $(MODFLAGS) dog.cpp cat.cpp -o animal
clean:
          rm -f foo $(OBJECTS)

when I create a shared object using make animal, this is what I get -

bash-2.05$ make animal
g++ -g -Wall -fpic -shared dog.cpp cat.cpp -o animal
ld: fatal: symbol `create' is multiply-defined:
        (file /var/tmp/ccgDUpwo.o type=FUNC; file /var/tmp/ccv0VjHp.o type=FUNC);
ld: fatal: symbol `destroy' is multiply-defined:
        (file /var/tmp/ccgDUpwo.o type=FUNC; file /var/tmp/ccv0VjHp.o type=FUNC);
ld: fatal: File processing errors. No output written to animal
collect2: ld returned 1 exit status
make: *** [animal] Error 1

I understand that there are multiple definitions of method create() and destroy() and hence the error. But at the same time, I could not use any class specific create() method in main.cpp because doing that will not make it generic. I am keeping create() and destroy() functions outside the class definition. I am also using extern "C" to make sure that compiler does not add name mangling, and keeps symbol name in shared libraries same as the function name.

Can somebody please give me some hints about how to go about this problem ? Any changes that could be done in the class design ?

Thanks for being patient in reading the code above.

- Onkar Deshpande

A: 

When calling dlsym(), you could dynamically create the name of a function instead of using the fixed name "create". By prepending the name of the object you're trying to create, you could have your main program look for dog_create or cat_create.

I also noticed that your call to new_animal passes a char *, yet the definitions of the corresponding create functions take no parameters. You will want to ensure that these are consistent (that is, both the functions and the calls to them use the same types of parameters).

Greg Hewgill
A: 

THe problem is exactly what the linker tells you: how to choose a create function between the two offered definitions?

So you could make it easier on the linker by using different symbols. You can manually choose for e.g. dog_create, cat_create, but you could also just write a template function and instantiate that for the cat and dog types.

// an abstract base factory class
struct Creator {
    virtual Animal* create() const = 0;
    virtual ~Creator(){};
}

template<typename tAnimal> struct TCreator : public Creator {
    tAnimal* create() const { return new tAnimal(); }
    // note that the return type can be covariant in C++
}

...

map<string, Creator*> AnimalMap;

void initialize() {
 AnimalMap["dog"] = new TCreator<dog>;
 AnimalMap["cat"] = new TCreator<cat>;
}

This way, you won't need a create method per animal.

xtofl
A: 

Now next to the template-based answer I provided, you can also solve the "two symbols for the linker" problem by not linking the cat.cpp and dog.cpp into one shared "animal" object. I guess that's what you ultimately want to do: being able to add animal types at runtime.

This can be achieved by creating a dog.so and cat.so, and later on a swan.so, with each just one create function, which boils down to reordering your makefile to `make a separate target for the cat.cpp and dog.cpp. Maybe you could create a rule for "ANIMALOBJECTS" next to the main object.

Then your main function has to load the create and destroy symbols from each shared object separately.

xtofl
A: 

xtofl and Greg Hewgill,

Thanks a lot for your answers, but I am still facing some problems with the design. As per xofl's suggestion, I created two different shared objects for cat and dog and it resolved the problem of multiple references. But in doing that, I have also changed the class design and removed extern "C" keywords from create() and destroy() methods. I did this because I wanted an implementation of the virtual create() and virtual destroy() of the base class Animal. The result was quite clear, the compiler must have used name mangling and my main() failed in resolving the calls in dlsym. Here is the modified code (I am really sorry to post similar code again and again, but I lost almost 2-3 days but still could not find the correct way.)

creator.hpp -

#ifndef CREATOR_HPP
#define CREATOR_HPP

#include <string>

class Animal {

     public :
     virtual string operator() (const string &animal_name) const = 0;
     virtual void eat() const = 0;
     virtual ~Animal() { }
};

class AnimalCreator {
     public :
     virtual Animal *create(const string &) = 0;
     virtual ~AnimalCreator() { }
};

typedef Animal* create_animal_t();
typedef void destroy_animal_t(Animal *);
#endif

cat.hpp -

#ifndef CAT_HPP
#define CAT_HPP

#include "creator.hpp"
#include <iostream>
#include <string>

class cat : public Animal {

     public :
          string operator() (const string &animal_name) const { return "In cat () operator"; }
          void eat() const { cout << "cat is eating" << endl; }
};

class catCreator : public AnimalCreator {
     public :
     Animal *create(const string &);
     void destroy(Animal *);
}theCatCreator;

#endif

cat.cpp -

#include "cat.hpp"
#include <iostream>

using namespace std;

Animal * catCreator :: create(const string &animal_name) {
     cout << "Creating " << animal_name << " ..." << endl;
     return new cat;
}

void catCreator :: destroy(Animal* a) {
     delete a;
}

dog.hpp -

#ifndef DOG_HPP
#define DOG_HPP

#include <string>
#include "creator.hpp"

class dog : public Animal {
     public:
     string operator() (const string &animal_name) const { return "In dog"; }
     void eat() const { cout << "Dog is eating" << endl; }
};

class dogCreator : public AnimalCreator {
     public:

           Animal *create(const string &);
           void destroy(Animal *);

}theDogCreator;

#endif

dog.cpp -

#include "dog.hpp"
#include <iostream>

using namespace std;

Animal * dogCreator::create(const string &animal_name) {
     cout << "Creating " << animal_name << "..." << endl;
     return new dog;
}

void dogCreator::destroy(Animal *aa) {
     delete aa;
}

main.cpp - This is a really bad code and I have lost myself quite a few times in it. I really need to fix a lot of part here.

#include "creator.hpp"
#include "cat.hpp"
#include "dog.hpp"

#include <iostream>
#include <string>
#include <map>
#include <dlfcn.h>

map<string, AnimalCreator *> AnimalMap;

void initialize() {
     AnimalMap["dog"] = &theDogCreator;
     AnimalMap["cat"] = &theCatCreator;
}

Animal * new_animal(string animal_tt) {
     static bool isInitialised (false);
     if (!isInitialised) {
          initialize();
          isInitialised = true;
     }

     AnimalCreator *theAnimalCreator = AnimalMap[animal_tt];
     if (!theAnimalCreator) {
          cout << "error: " << animal_tt << " not registerd" << endl;
          exit(1);
     }

     Animal *theAnimal = theAnimalCreator->create(animal_tt);
     return theAnimal;
}

int main() {

     void *animal_dog = dlopen("dog", RTLD_LAZY);
     if (!animal_dog) {
          cout << "error is dlopen" << endl;
      exit(1);
     }
     Animal* (*register_class)(const string &);
     register_class = (Animal* (*) (const string &)) dlsym(animal_dog, "create");
      if (!register_class) {
          cout << "error is dlsym create of dog" << endl;
          exit(1);
     }
     (*register_class)("dog");

     destroy_animal_t* destroy_class = (destroy_animal_t*) dlsym(animal_dog, "destroy");
      if (!destroy_class) {
          cout << "error is dlsym destroy" << endl;
          exit(1);
     }

     Animal *a = new_animal("dog");
     a->eat();
     (*destroy_class)(a);
     dlclose(animal_dog);

     void *animal_cat = dlopen("cat", RTLD_LAZY);
     if (!animal_cat) {
          cout << "error is dlopen" << endl;
          exit(1);
     }

     register_class = (Animal* (*) (const string &)) dlsym(animal_cat, "create");
      if (!register_class) {
          cout << "error is dlsym create" << endl;
          exit(1);
     }
     (*register_class)("cat");
     destroy_class = (destroy_animal_t*) dlsym(animal_cat, "destroy");
      if (!destroy_class) {
          cout << "error is dlsym destroy" << endl;
          exit(1);
     }

     Animal *b = new_animal("cat");
     b->eat();
     (*destroy_class)(b);
     dlclose(animal_cat);

     return 0;
}

Makefile -

# macros
CC = g++
CFLAGS = -g -Wall
MODFLAGS = -fpic -shared
LDFLAGS = -ldl
OBJECTS = main.o dog cat

# targets
all:      foo
foo:      $(OBJECTS)
          $(CC) -o foo $(OBJECTS) $(LDFLAGS)
dog:      dog.cpp
          $(CC) $(CFLAGS) $(MODFLAGS) dog.cpp -o dog
cat:      cat.cpp
          $(CC) $(CFLAGS) $(MODFLAGS) cat.cpp -o cat
clean:
          rm -f foo $(OBJECTS)

Here is the output that I get -

bash-2.05$ echo $LD_LIBRARY_PATH
/opt/csw/lib:/usr/local/lib/gtk-2.0/2.4.0/immodules:/usr/local/lib/gtk-2.0/2.4.0/loaders:/usr/local/lib/gtk-2.0/2.4.0/engines:/usr/local/lib:/usr/lib:/usr/openwin/lib:/usr/dt/lib:/opt/sfw/lib:/opt/local/SUNWspro/lib
bash-2.05$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
bash-2.05$ make clean
rm -f foo main.o dog cat
bash-2.05$ make cat
g++ -g -Wall -fpic -shared cat.cpp -o cat
bash-2.05$ make dog
g++ -g -Wall -fpic -shared dog.cpp -o dog
bash-2.05$ make
g++    -c -o main.o main.cpp
g++ -o foo main.o dog cat -ldl
bash-2.05$ ./foo
error is dlsym create of dog

Thus,I think this problem is because I am not using extern "C" as I have done in the last post. Can anyone give me some code snippets of how should I call those extern "C" methods from my main.cpp ?

Onkar Deshpande
A: 

You mixed two proposed solutions: either use the extern "C" linkage for the create and destroy functions - if you want runtime flexibility (e.g. loading more modules on-the-fly), or use a compiled-in Factory Function, where you don't need to dynamically load symbols at all.

Now you ended up with two shared objects that export no creator objects at all: the code

struct X { int i; double d; } theX;

only declares a symbol theX to whatever cpp file includes the header containing it.

It should also be defined somewhere, either in the X.cpp file, or, preferably, using the proposed template class, in the main/creators.cpp file.

But my guess is you want the dynamic behaviour, so this is my proposal:

  1. go back to the extern "C" Animal* create() version
  2. keep your makefile to have a "dog" and a "cat" shared object
xtofl
A: 

xtofl and Greg, Thanks a lot for your replies. It would not have been possible without your suggestions. One last time, I am eating a lot of place on this page to share the final code. I really appreciate your active participation in answering my doubts.

creator.hpp -

#ifndef CREATOR_HPP
#define CREATOR_HPP

#include <string>

class Animal {

     public :
     virtual void eat() const = 0;
     virtual ~Animal() { }
};

class AnimalCreator {
     public :
     virtual Animal *create(const string &) = 0;
     virtual ~AnimalCreator() { }
};

typedef Animal* create_animal_t(const string &);
typedef void destroy_animal_t(Animal *);
typedef void eat_animal_t();

#endif

cat.hpp -

#ifndef CAT_HPP
#define CAT_HPP

#include "creator.hpp"
#include <iostream>
#include <string>

class cat : public Animal {

     public :
          virtual void eat() const;
};

class catCreator : public AnimalCreator {
     public :
     Animal *create(const string &);
     void destroy(Animal *);
}theCatCreator;

#endif

cat.cpp -

#include "cat.hpp"
#include <iostream>

using namespace std;

extern "C" Animal *create_extern_cat(const string &str) {
     cout << "Creating " << str << "..." << endl;
     return new cat;
}

extern "C" void destroy_extern_cat(Animal* a) {
     cout<< "Destroying cat"<< endl;
     delete a;
}

extern "C" void eat_extern_cat() {
     cout << "Cat is eating" << endl;
}

Animal * catCreator :: create(const string &animal_name) {
     return create_extern_cat(animal_name);
}

void catCreator :: destroy(Animal *aa) {
     destroy_extern_cat(aa);
}
void cat :: eat() const {
     eat_extern_cat();
}

dog.hpp -

#ifndef DOG_HPP
#define DOG_HPP

#include <string>
#include "creator.hpp"

class dog : public Animal {
     public:
     virtual void eat() const;
};

class dogCreator : public AnimalCreator {
     public:
           Animal *create(const string &);
           void destroy(Animal *);
}theDogCreator;

#endif

dog.cpp -

#include "dog.hpp"
#include <iostream>

using namespace std;

extern "C" Animal *create_extern_dog(const string &str) {
     cout << "Creating  " << str << "..." << endl;
     return new dog;
}

extern "C" void destroy_extern_dog(Animal *aa) {
     cout<<"destroying dog" << endl;
     delete aa;
}

extern "C" void eat_extern_dog() {
     cout << "dog is eating" << endl;
}

Animal * dogCreator::create(const string &animal_name) {
     return create_extern_dog(animal_name);
}

void dogCreator::destroy(Animal *a) {
     destroy_extern_dog(a);
}

void dog :: eat() const{
     eat_extern_dog();
}

main.cpp -

#include "creator.hpp"
#include "cat.hpp"
#include "dog.hpp"

#include <iostream>
#include <string>
#include <map>
#include <dlfcn.h>

map<string, AnimalCreator *> AnimalMap;

void initialize() {
     AnimalMap["dog"] = &theDogCreator;
     AnimalMap["cat"] = &theCatCreator;
}

Animal * new_animal(const string &animal_tt) {
     static bool isInitialised (false);
     if (!isInitialised) {
          initialize();
          isInitialised = true;
     }
     AnimalCreator *theAnimalCreator = AnimalMap[animal_tt];
     if (!theAnimalCreator) {
          cout << "error: " << animal_tt << " not registerd" << endl;
          exit(1);
     }
     Animal *theAnimal = theAnimalCreator->create(animal_tt);
     return theAnimal;
}

int main() {

     destroy_animal_t* destroy_class;
     eat_animal_t* eat_class;
     Animal* (*register_class)(const string &);

     void *animal_cat = dlopen("cat", RTLD_LAZY);
     if (!animal_cat) {
          cout << "error is dlopen" << endl;
          exit(1);
     }

     register_class = (create_animal_t *) dlsym(animal_cat, "create_extern_cat");
      if (!register_class) {
          cout << "error is dlsym create" << endl;
          exit(1);
     }
     destroy_class = (destroy_animal_t*) dlsym(animal_cat, "destroy_extern_cat");
      if (!destroy_class) {
          cout << "error is dlsym destroy" << endl;
          exit(1);
     }

     eat_class = (eat_animal_t*) dlsym(animal_cat, "eat_extern_cat");
     if (!eat_class) {
          cout << "error in dlsym eat" << endl;
          exit(1);
     }

     Animal *b = new_animal("cat");
     b->eat();
     (*destroy_class)(b);
     dlclose(animal_cat);

     void *animal_dog = dlopen("dog", RTLD_LAZY);
     if (!animal_dog) {
          cout << "error is dlopen" << endl;
          exit(1);
     }
     register_class = (create_animal_t *) dlsym(animal_dog, "create_extern_dog");
      if (!register_class) {
          cout << "error is dlsym create of dog" << endl;
          exit(1);
     }
     destroy_class = (destroy_animal_t*) dlsym(animal_dog, "destroy_extern_dog");
      if (!destroy_class) {
          cout << "error is dlsym destroy" << endl;
          exit(1);
     }

     eat_class = (eat_animal_t*) dlsym(animal_dog, "eat_extern_dog");
     if (!eat_class) {
          cout << "error in dlsym eat" << endl;
          exit(1);
     }
     Animal *a = new_animal("dog");
     a->eat();
     (*destroy_class)(a);
     dlclose(animal_dog);

     return 0;
}

Makefile -

# macros
CC = g++
CFLAGS = -g -Wall
MODFLAGS = -fpic -shared
LDFLAGS = -ldl
OBJECTS = main.o dog cat

# targets
all:      foo
foo:      $(OBJECTS)
          $(CC) -o foo $(OBJECTS) $(LDFLAGS)
dog:      dog.cpp
          $(CC) $(CFLAGS) $(MODFLAGS) dog.cpp -o dog
cat:      cat.cpp
          $(CC) $(CFLAGS) $(MODFLAGS) cat.cpp -o cat
clean:
          rm -f foo $(OBJECTS)

Output -

bash-2.05$ echo $LD_LIBRARY_PATH
/opt/csw/lib:/usr/local/lib/gtk-2.0/2.4.0/immodules:/usr/local/lib/gtk-2.0/2.4.0/loaders:/usr/local/lib/gtk-2.0/2.4.0/engines:/usr/local/lib:/usr/lib:/usr/openwin/lib:/usr/dt/lib:/opt/sfw/lib:/opt/local/SUNWspro/lib
bash-2.05$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
bash-2.05$ ls
cat          cat.hpp      dog          dog.hpp      main.cpp     Makefile
cat.cpp      creator.hpp  dog.cpp      foo          main.o       README
bash-2.05$ make clean
rm -f foo main.o dog cat
bash-2.05$ make
g++    -c -o main.o main.cpp
g++ -g -Wall -fpic -shared dog.cpp -o dog
g++ -g -Wall -fpic -shared cat.cpp -o cat
g++ -o foo main.o dog cat -ldl
bash-2.05$ ./foo
Creating cat...
Cat is eating
Destroying cat
Creating  dog...
dog is eating
destroying dog
bash-2.05$
Onkar Deshpande
Nice that you got it working. Now, imho, it's time to look what parts of your system you can throw away: is any creator object *really* needed, can you make the system more generic by e.g. calling the creator/destroyer functions "create" and "destroy" for each animal type? Once you have an animal object (Cat or Dog), does it need the "cat_eat" extern "C" function - couldn't you call "animal->eat()"? ...
xtofl
And next to that, if you feel we really helped, you are supposed to vote the answers up. Or if you're in a good mood, even approve one.
xtofl
xtofl, I did not get your question. Because in new_animal() I am calling create() depending on the type of the current object and also in main() I am using cat->eat() or dog->eat(). Could you please elaborate more on your suggestion ?I tried voting up the answers but it needs minimum 15 reputations, which I don't have right now :( but I have approved the answers.
Onkar Deshpande