【问题标题】:Multiple Symbol Reference problem in shared library of a factory design pattern工厂设计模式共享库中的多符号引用问题
【发布时间】:2009-07-25 03:39:21
【问题描述】:

我正在尝试编写工厂设计模式的 C++ 实现。我也想使用共享对象和动态加载来做到这一点。我正在实现一个名为 new_animal() 的函数,它传递了一个字符串。如果字符串为“dog”,则需要查看是否有一个class dog注册到共享对象中,并创建一个dog对象。如果字符串是“cat”,它需要找到注册的 class cat 并返回它的一个对象。函数 new_animal() 事先并不知道将传递给它的字符串。因此,如果传递了具有相应未注册类的字符串,则会出错。这是代码 -

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;
}

生成文件 -

# 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)

当我使用 make animal 创建共享对象时,这就是我得到的 -

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

我知道方法 create() 和 destroy() 有多个定义,因此会出现错误。但同时,我不能在 main.cpp 中使用任何特定于类的 create() 方法,因为这样做不会使其通用。我将 create() 和 destroy() 函数保留在类定义之外。我还使用 extern "C" 来确保编译器不会添加名称修饰,并保持共享库中的符号名称与函数名称相同。

有人可以给我一些关于如何解决这个问题的提示吗?可以在类设计中进行任何更改吗?

感谢您耐心阅读上面的代码。

-Onkar Deshpande

【问题讨论】:

    标签: c++ design-patterns oop shared-libraries


    【解决方案1】:

    问题正是链接器告诉您的:如何在两个提供的定义之间选择create 函数?

    因此,您可以通过使用不同的符号来简化链接器。您可以手动选择例如dog_createcat_create,但您也可以只编写一个模板函数并将其实例化为 catdog 类型。

    // 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>;
    }
    

    这样,您将不需要一个create 方法每只动物

    【讨论】:

      【解决方案2】:

      在调用dlsym() 时,您可以动态创建函数的名称,而不是使用固定名称"create"。通过添加您尝试创建的对象的名称,您可以让您的主程序查找dog_createcat_create

      我还注意到您对new_animal 的调用传递了char *,但相应create 函数的定义不带参数。您需要确保它们是一致的(也就是说,函数和对它们的调用都使用相同类型的参数)。

      【讨论】:

        【解决方案3】:

        现在,在我提供的基于模板的答案旁边,您还可以通过不将 cat.cpp 和 dog.cpp 链接到一个共享的“动物”对象来解决“链接器的两个符号”问题。我想这就是你最终想要做的:能够在运行时添加动物类型。

        这可以通过创建一个 dog.so 和 cat.so 来实现,然后创建一个 swan.so,每个只有 一个 create 函数,归结为将您的 makefile 重新排序为`为 cat.cpp 和 dog.cpp 创建一个单独的目标。也许您可以在主对象旁边为“ANIMALOBJECTS”创建一个规则。

        然后你的主函数必须分别从每个共享对象加载createdestroy 符号。

        【讨论】:

          【解决方案4】:

          您混合了两种建议的解决方案:要么使用 createdestroy 函数的 extern "C" 链接 - 如果你想要运行时灵活性(例如,即时加载更多模块),或者使用编译的工厂函数,您根本不需要动态加载符号。

          现在你得到了两个完全不导出创建者对象的共享对象:代码

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

          声明符号theX 到任何包含它的头文件的cpp 文件。

          它也应该在某个地方定义,或者在 X.cpp 文件中,或者最好使用建议的模板类,在 main/creators.cpp 文件中。

          但我猜你想要动态行为,所以这是我的建议:

          1. 回到extern "C" Animal* create() 版本
          2. 让您的 makefile 拥有“狗”和“猫”共享对象

          【讨论】:

            【解决方案5】:

            xtofl 和格雷格, 非常感谢您的回复。没有您的建议,这是不可能的。最后一次,我在这个页面上吃了很多地方来分享最终的代码。非常感谢您积极参与回答我的疑问。

            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;
            }
            

            生成文件 -

            # 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)
            

            输出 -

            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$
            

            【讨论】:

            • 很高兴你得到它的工作。现在,恕我直言,是时候看看您可以丢弃系统的哪些部分了:是否需要任何创建者对象真的,您可以通过例如使系统更通用吗?为每种动物类型调用创建者/破坏者函数“创建”和“破坏”?一旦你有了一个动物对象(猫或狗),它是否需要“cat_eat”外部“C”函数——你不能调用“animal->eat()”吗? ...
            • 此外,如果您觉得我们真的帮了您,您应该投票赞成。或者,如果您心情愉快,甚至可以批准一个。
            • xtofl,我没有得到你的问题。因为在 new_animal() 中我根据当前对象的类型调用 create(),并且在 main() 中我使用的是 cat->eat() 或 dog->eat()。您能否详细说明您的建议?我尝试对答案进行投票,但它需要至少 15 个声誉,而我现在没有 :( 但我已经批准了答案。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-11-04
            • 1970-01-01
            • 1970-01-01
            • 2016-12-24
            • 1970-01-01
            • 2011-01-07
            • 1970-01-01
            相关资源
            最近更新 更多