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
#pragma once | |
#include <memory> | |
class HTTPServerImpl; | |
class HTTPServer | |
{ | |
public: | |
HTTPServer(unsigned short port); | |
~HTTPServer(); | |
unsigned short GetPort() const; | |
private: | |
std::unique_ptr<HTTPServerImpl> m_Impl; | |
}; |
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 "HTTPServerClassic.h" | |
#include "HTTPServerImpl.h" | |
HTTPServer::HTTPServer(unsigned short port) | |
: m_Impl(new HTTPServerImpl(port)) | |
{ | |
} | |
HTTPServer::~HTTPServer() | |
{ | |
} | |
unsigned short HTTPServer::GetPort() const | |
{ | |
return m_Impl->GetPort(); | |
} |
It has two drawbacks:
- inhibits function inlining
- has an extra heap allocation and pointer chase
This allocation can be avoided by a simple trade-off with the PImpl idiom. Why allocating the HTTPServerImpl instance, instead of storing it in the facade object? This is because C++ requires to see the declaration of HTTPServerImpl to allow it to be stored by value. But we can store a C++ object in every memory chunk large enough to hold the its data and respects its alignment requirements. So instead of storing HTTPServerImpl pointer in the facade, we can store a memory chunk that is interpreted as an instance of HTTPServerImpl.This concept can be easily generalized in an reusable template:
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
#pragma once | |
#include <new> | |
#include <utility> | |
// Visual Studio 2010 does not support std::max_align_t, | |
// so use an arbitrary default and leave it for clients to specify better value | |
template <typename T, size_t size, size_t alignment = sizeof(unsigned long long)> | |
class Impl | |
{ | |
public: | |
Impl() | |
{ | |
new (Get()) T; | |
} | |
template <typename Arg1> | |
Impl(Arg1&& arg1) | |
{ | |
new (Get()) T(std::forward<Arg1>(arg1)); | |
} | |
template <typename Arg1, typename Arg2> | |
Impl(Arg1&& arg1, Arg2&& arg2) | |
{ | |
new (Get()) T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2)); | |
} | |
Impl& operator=(const Impl& rhs) | |
{ | |
*Get() = *rhs.Get(); | |
} | |
Impl& operator=(Impl&& rhs) | |
{ | |
*Get() = std::move(*rhs.Get()); | |
} | |
~Impl() | |
{ | |
static_assert(sizeof(T) <= size, | |
"Implementation instance does not fit in the buffer"); | |
static_assert(alignment % std::alignment_of<T>::value == 0, | |
"Implementation instance has incompatible alignment requirements"); | |
Get()->~T(); | |
} | |
T* Get() | |
{ | |
return reinterpret_cast<T*>(&m_Buffer); | |
} | |
const T* Get() const | |
{ | |
return reinterpret_cast<const T*>(&m_Buffer); | |
} | |
T* operator->() | |
{ | |
return Get(); | |
} | |
const T* operator->() const | |
{ | |
return Get(); | |
} | |
private: | |
typedef typename std::aligned_storage<size, alignment>::type AlignedStorage; | |
AlignedStorage m_Buffer; | |
}; |
And the HTTPServer becomes:
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 "Impl.h" | |
class HTTPServerImpl; | |
class HTTPServer | |
{ | |
public: | |
HTTPServer(unsigned short port); | |
~HTTPServer(); | |
unsigned short GetPort() const; | |
private: | |
Impl<HTTPServerImpl, 8> m_Impl; | |
}; |
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 "HTTPServer.h" | |
#include "HTTPServerImpl.h" | |
HTTPServer::HTTPServer(unsigned short port) | |
: m_Impl(port) | |
{ | |
} | |
HTTPServer::~HTTPServer() | |
{ | |
} | |
unsigned short HTTPServer::GetPort() const | |
{ | |
return m_Impl->GetPort(); | |
} |
- The alignment problems are mitigated by C++11 support for alignment.
- Writing operator= is not harder than writing it in general
- The extra memory consumption is acceptable for small number of instances, given the better cache coherency.
The classical implementation looks like:
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
?GetPort@HTTPServer@@QBEGXZ PROC ; HTTPServer::GetPort, COMDAT | |
; _this$ = ecx | |
mov eax, DWORD PTR [ecx] | |
mov ax, WORD PTR [eax] | |
ret 0 |
And the "twisted" one:
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
?GetPort@HTTPServer@@QBEGXZ PROC ; HTTPServer::GetPort, COMDAT | |
; _this$ = ecx | |
mov ax, WORD PTR [ecx] | |
ret 0 |
Seems like it does.
Of course, this technique breaks the PImpl idiom and might be considered a hack. Every time the HTTPServerImpl grows beyond the hard-coded size or its alignment requirements change, we have to change the definition of the facade and recompile all the source files depending on the HTTPServer.h, but given the advantages, this is an acceptable trade-off for many situations.
Literature:
- John Lakos; Large-Scale C++ Software Design; Addison-Wesley Longman, 1996
- Herb Sutter; Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions; Addison-Wesley Longman, 2000
No comments:
Post a Comment