Why use interface instead of pimpl
The opaque pointer (pimpl) idiom has been inherited from C language where it is used to encapsulate implementation details. However, both old-school and "modern" C++ dispense you from writing some ugly code, and allow to use interfaces with object factories.
The example of implementation with both pimpl and interface approach is as follows.
pimpl.hpp: pimpl implementation header
#include <string>
#include <memory>
using namespace std;
class book
{
public:
book(const string& title);
book(const string& title, const string& author_name, const int page_count);
// If you use pimpl with unique_ptr, you need to declare a destructor
// which should be implemented in '*.cpp' file
~book();
public:
// Main interface
string title() const;
string author_name() const;
int page_count() const;
private:
class book_impl; // Defined and implemented in '*.cpp' file
unique_ptr<book::book_impl> m_impl; // Opaque pointer
};
using book_ptr = unique_ptr<book>;
pimpl.cpp: implementation
#include "pimpl.hpp"
// Here we include some headers i.e. of third-party libraries
// which are required for implementation only
// Once being published, the 'pimpl.hpp' does not depend on them
class book::book_impl
{
public:
book_impl(const string& name, const string& author_name, const int page_count)
: m_title(name), m_author_name(author_name), m_page_count(page_count)
{}
// Main interface implementation
string title() const {
return m_title;
}
string author_name() const {
return m_author_name;
}
int page_count() const {
return m_page_count;
}
private:
string m_title;
string m_author_name;
int m_page_count;
};
// Now we need to code the redirection of ALL calls
book::book(const string& title)
: book(title, "Unknown", 1)
{}
book::book(const string& title, const string& author_name, const int page_count)
: m_impl(make_unique<book_impl>(title, author_name, page_count))
{}
book::~book()
{}
string book::title() const {
return m_impl->title();
}
string book::author_name() const {
return m_impl->author_name();
}
int book::page_count() const {
return m_impl->page_count();
}
no_pimpl.hpp: interface and factory header
#include <string>
#include <memory>
using namespace std;
class book_intf
{
public: // Main interface
virtual string title() const = 0;
virtual string author_name() const = 0;
virtual int page_count() const = 0;
public:
virtual ~book_intf() {}
};
using book_intf_ptr = unique_ptr<book_intf>;
class book_factory
{
public:
static book_intf_ptr create(const string& title);
static book_intf_ptr create(const string& title, const string& author_name, const int page_count);
};
no_pimpl.cpp: implementation
#include "no_pimpl.hpp"
class book_impl : public book_intf
{
public:
book_impl(const string& title)
: book_impl(title, "Unknown", 1)
{}
book_impl(const string& title, const string& author_name, const int page_count)
: m_title(title), m_author_name(author_name), m_page_count(page_count)
{}
public: // Main interface implementation
string title() const override {
return m_title;
}
string author_name() const override {
return m_author_name;
}
int page_count() const override {
return m_page_count;
}
private:
string m_title;
string m_author_name;
int m_page_count;
};
// Book factories
book_intf_ptr book_factory::create(const string& title) {
return move(make_unique<book_impl>(title));
}
book_intf_ptr book_factory::create(const string& title, const string& author_name, const int page_count) {
return move(make_unique<book_impl>(title, author_name, page_count));
}
As you can see, redirection of calls is not required when using interfaces, so you do not need to define functions signatures twice with copy-past coding.
Running both:
#include "pimpl.hpp"
#include "no_pimpl.hpp"
#include <memory>
#include <iostream>
int main(int, char**) {
auto book1 = std::make_unique<book>("Modern old-school C++", "Gang of thousands", 255);
cout << "Title: " << book1->title()
<< "\nAuthor: " << book1->author_name()
<< "\nPages: " << book1->page_count()
<< endl;
book_intf_ptr book2 = book_factory::create("Modern old-school C++", "Gang of thousands", 255);
cout << "Title: " << book2->title()
<< "\nAuthor: " << book2->author_name()
<< "\nPages: " << book2->page_count()
<< endl;
return 0;
}
About factories
We can define factories at least several different ways:
- separate factory class with static methods
- separate factory class with non-static methods (need when additional initializations/settings are required before create an instance)
- static method within interface class (may be a small harm when derive a new interface from this one)
- namespace functions (simplest but minimally abstract)
blog comments powered by Disqus