My question, as the title mentioned, is obvious, and I describe the scenario in details. There is a class named singleton implemented by singleton pattern as following, in file singleton.h:
/* * singleton.h * * Created on: 2011-12-24 * Author: bourneli */ #ifndef SINGLETON_H_ #define SINGLETON_H_ class singleton { private: singleton() {num = -1;} static singleton* pInstance; public: static singleton& instance() { if (NULL == pInstance) { pInstance = new singleton(); } return *pInstance; } public: int num; }; singleton* singleton::pInstance = NULL; #endif /* SINGLETON_H_ */
then, there is a plugin called hello.cpp as following:
#include <iostream> #include "singleton.h" extern "C" void hello() { std::cout << "singleton.num in hello.so : " << singleton::instance().num << std::endl; ++singleton::instance().num; std::cout << "singleton.num in hello.so after ++ : " << singleton::instance().num << std::endl; }
you can see that the plugin call the singleton and change the attribute num in the singleton.
last, there is a main function use the singleton and the plugin as following:
#include <iostream> #include <dlfcn.h> #include "singleton.h" int main() { using std::cout; using std::cerr; using std::endl; singleton::instance().num = 100; // call singleton cout << "singleton.num in main : " << singleton::instance().num << endl;// call singleton // open the library void* handle = dlopen("./hello.so", RTLD_LAZY); if (!handle) { cerr << "Cannot open library: " << dlerror() << '\n'; return 1; } // load the symbol typedef void (*hello_t)(); // reset errors dlerror(); hello_t hello = (hello_t) dlsym(handle, "hello"); const char *dlsym_error = dlerror(); if (dlsym_error) { cerr << "Cannot load symbol 'hello': " << dlerror() << '\n'; dlclose(handle); return 1; } hello(); // call plugin function hello cout << "singleton.num in main : " << singleton::instance().num << endl;// call singleton dlclose(handle); }
and the makefile is following:
example1: main.cpp hello.so $(CXX) $(CXXFLAGS) -o example1 main.cpp -ldl hello.so: hello.cpp $(CXX) $(CXXFLAGS) -shared -o hello.so hello.cpp clean: rm -f example1 hello.so .PHONY: clean
so, what is the output? I thought there is following:
singleton.num in main : 100 singleton.num in hello.so : 100 singleton.num in hello.so after ++ : 101 singleton.num in main : 101
however, the actual output is following:
singleton.num in main : 100 singleton.num in hello.so : -1 singleton.num in hello.so after ++ : 0 singleton.num in main : 100
It proves that there are two instances of the singleton class.
Why?
Well-designed singleton can have only one instance per application. Creating of multiple instances is a mistake in the application design.
If you don't want to use one of those containers, just create all instances of objects you'll need to be singleton in one place, and inject them to other objects via their constructor. You may only need one instance throughout the lifetime of your application, but you don't need the Singleton pattern for that.
new Singleton() does not have a matching delete , so yea, you are leaking a resource. You'll get memory back when the program closes, but not all resources are returned when the program ends.
The purpose of Singleton pattern is to make sure that, at any given time, there is ONE AND ONLY ONE instance of the class, within the current JVM. So, if Singleton pattern is implemented flawlessly, then, it's not at all possible to create multiple instances of a Singleton class.
First, you should generally use -fPIC
flag when building shared libraries.
Not using it "works" on 32-bit Linux, but would fail on 64-bit one with an error similar to:
/usr/bin/ld: /tmp/ccUUrz9c.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
Second, your program will work as you expect after you add -rdynamic
to the link line for the main executable:
singleton.num in main : 100 singleton.num in hello.so : 100 singleton.num in hello.so after ++ : 101 singleton.num in main : 101
In order to understand why -rdynamic
is required, you need to know about the way dynamic linker resolves symbols, and about the dynamic symbol table.
First, let's look at the dynamic symbol table for hello.so
:
$ nm -C -D hello.so | grep singleton 0000000000000b8c W singleton::instance() 0000000000201068 B singleton::pInstance 0000000000000b78 W singleton::singleton()
This tells us that there are two weak function definitions, and one global variable singleton::pInstance
that are visible to the dynamic linker.
Now let's look at the static and dynamic symbol table for the original example1
(linked without -rdynamic
):
$ nm -C example1 | grep singleton 0000000000400d0f t global constructors keyed to singleton::pInstance 0000000000400d38 W singleton::instance() 00000000006022e0 B singleton::pInstance 0000000000400d24 W singleton::singleton() $ nm -C -D example1 | grep singleton $
That's right: even though the singleton::pInstance
is present in the executable as a global variable, that symbol is not present in the dynamic symbol table, and therefore "invisible" to the dynamic linker.
Because the dynamic linker "doesn't know" that example1
already contains a definition of singleton::pInstance
, it doesn't bind that variable inside hello.so
to the existing definition (which is what you really want).
When we add -rdynamic
to the link line:
$ nm -C example1-rdynamic | grep singleton 0000000000400fdf t global constructors keyed to singleton::pInstance 0000000000401008 W singleton::instance() 00000000006022e0 B singleton::pInstance 0000000000400ff4 W singleton::singleton() $ nm -C -D example1-rdynamic | grep singleton 0000000000401008 W singleton::instance() 00000000006022e0 B singleton::pInstance 0000000000400ff4 W singleton::singleton()
Now the definition of singleton::pInstance
inside the main executable is visible to the dynamic linker, and so it will "reuse" that definition when loading hello.so
:
LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance 31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With