I am writing some classes in C++ to act as a personal small library and I encountered a problem.
Some of my objects make us of third-party libraries written in magnificent C style.
That means those libraries have functions like apiInit() and apiCleanup(), where the former must be called before any of the actual api functions and the latter must be called when you are not going to use them anymore.
What I want is to provide the classes that need a library with an access point to its functions ensuring apiInit() is called when the first needing class is created, or at least before any api function is used, and apiCleanup() is called when the last instance that uses the api is destroyed.
Keep in mind there are more than one class that makes use of a single library.
I could come with two solutions:
First, the obvious one, make the provider a singleton:
#include <iostream>
using namespace std;
class ContextProvider {
ContextProvider() {
cout << "Initializing API" << endl;
}
ContextProvider(ContextProvider const& rhs) = delete;
ContextProvider& operator=(ContextProvider const& rhs) = delete;
public:
~ContextProvider() {
cout << "Cleaning API" << endl;
}
static ContextProvider& getInstance() {
static ContextProvider instance;
return instance;
}
void useContext() {
cout << "Using API" << endl;
}
};
class ContextUser1 {
public:
ContextUser1() {
}
void doSomething() {
ContextProvider::getInstance().useContext();
}
};
class ContextUser2 {
public:
ContextUser2() {
}
void doSomethingElse() {
ContextProvider::getInstance().useContext();
}
};
The other one would be to keep a counter of context users, like so:
#include <iostream>
using namespace std;
class ContextProvider {
static unsigned int userCounter;
public:
ContextProvider() {
if (userCounter == 0)
cout << "Initializing API" << endl;
++userCounter;
}
~ContextProvider() {
--userCounter;
if (userCounter == 0)
cout << "Cleaning API" << endl;
}
void useContext() {
cout << "Using API" << endl;
}
};
unsigned int ContextProvider::userCounter = 0;
class ContextUser1 {
ContextProvider cp;
public:
ContextUser1() {
cp = ContextProvider();
}
void doSomething() {
cp.useContext();
}
};
class ContextUser2 {
ContextProvider cp;
public:
ContextUser2() {
cp = ContextProvider();
}
void doSomethingElse() {
cp.useContext();
}
};
int main() {
ContextUser1 cu11, cu12, cu13;
ContextUser2 cu21, cu22;
cu11.doSomething();
cu12.doSomething();
cu21.doSomethingElse();
cu22.doSomethingElse();
cu13.doSomething();
}
Both, when executed with the following main()
int main() {
ContextUser1 cu11, cu12, cu13;
ContextUser2 cu21, cu22;
cu11.doSomething();
cu12.doSomething();
cu21.doSomethingElse();
cu22.doSomethingElse();
cu13.doSomething();
}
yeld the expected result, that is:
Initializing API
Using API
Using API
Using API
Using API
Using API
Cleaning API
Now the obvious question is, which one is better, or which one should I use?
For example, some things that come to mind are...
Singleton method:
Advantages:
Disadvantages:
ContextProvider::getInstance().use()).Counter method:
Advantages:
cp.use()).Disadvantages:
I mainly ask this question because I don't know which of these advantages/disadvantages weight more, if there are things I didn't account for, or maybe there is an obvious third method I couldn't come up with that is inherently better than those two, or, who knows.
Thank you for your time!
I'd use your second approach, with the following modifications:
class ContextUser1 {
std::shared_ptr<ContextProvider> cp;
public:
ContextUser1(const std::shared_ptr<ContextProvider>& cp)
: cp(cp) {
}
void doSomething() {
cp->useContext();
}
};
Making the dependency explicit makes your code better in terms of being testable. Also, using shared_ptr takes care of counting, so you don't even need to do this yourself.
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