std::queue<int, std::vector<int>> queue_using_vector;
std::queue<int, std::list<int>> queue_using_list;
Here, the container is provided as a template and therefore must be declared during compilation time. What if, here comes the question, I want to be able to choose its implementation during runtime and not compilation time?
In this post, I want to show the closest solution to the problem above. Consider the following scenario. I want to build a queue that uses some container APIs. The container can be either implemented through an array or through a list. I want to be able to decide this during runtime.
The code below shows a sketch of this design pattern. First, you must create its container APIs through a purely abstract class, i.e., define container interface. Then, you implement the APIs using two implementations: using an array and using a list. Next, you define a concrete container class which takes in either one of the implementation pointer. Finally, you can now use this concrete container class to implement a queue.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <memory> | |
/* | |
* This code provides an example design pattern to achieve the following: | |
* | |
* We want to implement Container class using two different implementations | |
* We further want to select its implementation during runtime | |
* We also want to be able to do the same for any classes derived from Container | |
* We even want to be able to templatize on Container class | |
*/ | |
/** | |
* interface that all implementations must bind to | |
*/ | |
class ContainerInterface { | |
public: | |
virtual ~ContainerInterface() = default; | |
virtual void identify() const = 0; | |
virtual void push_back(int) = 0; | |
virtual void pop_back() = 0; | |
virtual size_t size() const = 0; | |
virtual int &front() = 0; | |
virtual int &back() = 0; | |
}; | |
/** | |
* one type of implementation | |
*/ | |
class Array : public ContainerInterface { | |
public: | |
Array() = default; | |
void identify() const override { std::cout << "array" << std::endl; } | |
void push_back(int x) override { /* ... */ } | |
void pop_back() override { /* ... */ } | |
size_t size() const override { /* ... */ } | |
int &front() override { /* ... */ } | |
int &back() override { /* ... */ } | |
private: | |
/* ... */ | |
}; | |
/** | |
* another type of implementation | |
*/ | |
class List : public ContainerInterface { | |
public: | |
List() = default; | |
void identify() const override { std::cout << "list" << std::endl; } | |
void push_back(int x) override { /* ... */ } | |
void pop_back() override { /* ... */ } | |
size_t size() const override { /* ... */ } | |
int &front() override { /* ... */ } | |
int &back() override { /* ... */ } | |
private: | |
/* ... */ | |
}; | |
/** | |
* a concrete container taking one implementation | |
*/ | |
class Container : public ContainerInterface { | |
public: | |
explicit Container(std::unique_ptr<ContainerInterface> impl) | |
: impl_{std::move(impl)} {} | |
void identify() const override { impl_->identify(); } | |
void push_back(int x) override { impl_->push_back(x); } | |
void pop_back() override { impl_->pop_back(); } | |
size_t size() const override { return impl_->size(); } | |
int &front() override { return impl_->front(); } | |
int &back() override { return impl_->back(); } | |
private: | |
std::unique_ptr<ContainerInterface> impl_; | |
}; | |
/** | |
* Demonstration of templated private inheritance | |
* Queue is implemented through Container APIs | |
*/ | |
template<typename C = Container> | |
class Queue : private C { | |
public: | |
explicit Queue(std::unique_ptr<ContainerInterface> impl) : | |
Container{std::move(impl)} { | |
C::identify(); | |
} | |
void push(int x) { /* ... */ } | |
void pop() { /* ... */ } | |
const int& top() const { /* ... */ } | |
size_t size() const { return C::size(); } | |
bool empty() const { return C::empty(); } | |
}; | |
/** | |
* Demonstration of public inheritance | |
* AdvancedContainer extends Container APIs | |
* but its implementations should only rely on Container APIs | |
* and should not depend on implementation specific of array or list | |
*/ | |
class AdvancedContainer : public Container { | |
public: | |
explicit AdvancedContainer(std::unique_ptr<ContainerInterface> impl) : | |
Container{std::move(impl)} {} | |
bool empty() const { return size() == 0; } | |
}; | |
int main(int argc, const char** argv) { | |
// two copies of implementations depending on user input, determined during run-time | |
std::unique_ptr<ContainerInterface> impl1, impl2; | |
if (std::strcmp(argv[1], "array") == 0) { | |
impl1 = std::unique_ptr<ContainerInterface>(new Array); | |
impl2 = std::unique_ptr<ContainerInterface>(new Array); | |
} | |
else { | |
impl1 = std::unique_ptr<ContainerInterface>(new List); | |
impl2 = std::unique_ptr<ContainerInterface>(new List); | |
} | |
// both Queue and AdvancedContainer implementations are determined during run-time | |
Queue<Container> queue{std::move(impl1)}; | |
AdvancedContainer advancedContainer{std::move(impl2)}; | |
advancedContainer.identify(); | |
return 0; | |
} | |
/* | |
* run example | |
* $ ./a.out array | |
* array | |
* array | |
* | |
* $ ./a.out list | |
* list | |
* list | |
*/ |
Excellent, This is an amazing superb article Keep Sharing this...
ReplyDeleteThanks a lot!!!!
Germany VPS Hosting