Created
January 24, 2013 17:08
-
-
Save mattyclarkson/4625200 to your computer and use it in GitHub Desktop.
Are the benefits of private implementations worth the performance trade offs.
This file contains hidden or 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 <cassert> | |
#include <chrono> | |
#include <iostream> | |
#include <memory> | |
#include <sstream> | |
#include <stdexcept> | |
#include <unordered_map> | |
/////////////////////////////////////////////////////////////////////////////// | |
#ifdef ENABLE_LOGGING | |
# define LOG(x) std::clog << x << std::endl | |
#else | |
# define LOG(x) while(0) {} | |
#endif | |
/////////////////////////////////////////////////////////////////////////////// | |
class Pimpl final { | |
#ifdef MAKE_IMPLEMENTATION_PUBLIC | |
public: | |
#else | |
private: | |
#endif | |
class PimplImpl; | |
public: | |
typedef std::string MapKey; | |
typedef std::string MapValue; | |
typedef std::unordered_map<MapKey, MapValue> Map; | |
public: | |
Pimpl(); | |
explicit Pimpl(Map&& map); | |
~Pimpl(); | |
Pimpl(const Pimpl& other); | |
Pimpl(Pimpl&& test); | |
#ifdef COPY_AND_SWAP | |
Pimpl& operator=(Pimpl other); | |
#else | |
Pimpl& operator=(const Pimpl& other); | |
Pimpl& operator=(Pimpl&& other); | |
#endif | |
const MapValue& operator[](const MapKey& key) const; | |
size_t Size() const noexcept; | |
void Clear() noexcept; | |
static constexpr const char * const TypeName(); | |
friend void swap(Pimpl& lhs, Pimpl& rhs); | |
friend void swap(PimplImpl& lhs, PimplImpl& rhs); | |
private: | |
std::unique_ptr<PimplImpl> pimpl_; | |
friend std::ostream& operator<<(std::ostream& stream, const Pimpl& object); | |
friend std::ostream& operator<<(std::ostream& stream, const PimplImpl& object); | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
class Pimpl::PimplImpl final { | |
public: | |
typedef Pimpl::MapKey MapKey; | |
typedef Pimpl::MapValue MapValue; | |
typedef Pimpl::Map Map; | |
public: | |
PimplImpl(); | |
explicit PimplImpl(Map&& map); | |
~PimplImpl(); | |
PimplImpl(const PimplImpl& other); | |
PimplImpl(PimplImpl&& test); | |
#ifdef COPY_AND_SWAP | |
PimplImpl& operator=(PimplImpl other); | |
#else | |
PimplImpl& operator=(const PimplImpl& other); | |
PimplImpl& operator=(PimplImpl&& other); | |
#endif | |
const MapValue& operator[](const MapKey& key) const; | |
size_t Size() const noexcept; | |
void Clear() noexcept; | |
static constexpr const char * const TypeName(); | |
friend void swap(PimplImpl& lhs, PimplImpl& rhs); | |
private: | |
friend std::ostream& operator<<(std::ostream& stream, const PimplImpl& object); | |
Map map_; | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
Pimpl::Pimpl() | |
: pimpl_(new PimplImpl()) { | |
LOG(" Default constructor called"); | |
}; | |
Pimpl::PimplImpl::PimplImpl() | |
: map_() { | |
LOG(" Implementation default constructor called"); | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
Pimpl::Pimpl(const Pimpl& other) | |
: pimpl_(new PimplImpl(*other.pimpl_)) { | |
LOG(" Copy constructor called"); | |
}; | |
Pimpl::PimplImpl::PimplImpl(const PimplImpl& other) | |
: map_(other.map_) { | |
LOG(" Implementation copy constructor called"); | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
Pimpl::Pimpl(Pimpl&& other) | |
: pimpl_(new PimplImpl(std::move(*other.pimpl_))) { | |
LOG(" Move constructor called"); | |
}; | |
Pimpl::PimplImpl::PimplImpl(PimplImpl&& other) | |
: map_(std::move(other.map_)) { | |
LOG(" Implmentation move constructor called"); | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
Pimpl::Pimpl(Map&& map) | |
: pimpl_(new PimplImpl(std::move(map))) { | |
LOG(" Map constructor called"); | |
}; | |
Pimpl::PimplImpl::PimplImpl(Map&& map) | |
: map_(std::move(map)) { | |
LOG(" Implmentation map constructor called"); | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
Pimpl::~Pimpl() { | |
LOG(" Destructor called"); | |
}; | |
Pimpl::PimplImpl::~PimplImpl() { | |
LOG(" Implementation destructor called"); | |
}; | |
/////////////////////////////////////////////////////////////////////////////// | |
#ifdef COPY_AND_SWAP | |
Pimpl& Pimpl::operator=(Pimpl other) { | |
LOG(" Copy and swap assignment called"); | |
using std::swap; | |
swap(this->pimpl_, other.pimpl_); | |
return *this; | |
} | |
Pimpl::PimplImpl& | |
Pimpl::PimplImpl::operator=(PimplImpl other) { | |
LOG(" Copy and swap assignment called"); | |
using std::swap; | |
swap(this->map_, other.map_); | |
return *this; | |
} | |
#else | |
Pimpl& Pimpl::operator=(const Pimpl& other) { | |
LOG(" Copy assignment called"); | |
if(&other != this) { | |
assert(this->pimpl_); | |
assert(other.pimpl_); | |
*this->pimpl_ = *other.pimpl_; | |
} | |
return *this; | |
} | |
Pimpl::PimplImpl& Pimpl::PimplImpl::operator=(const PimplImpl& other) { | |
LOG(" Implementation copy assignment called"); | |
if(&other != this) { | |
this->map_ = other.map_; | |
} | |
return *this; | |
} | |
#endif | |
/////////////////////////////////////////////////////////////////////////////// | |
#ifndef COPY_AND_SWAP | |
Pimpl& Pimpl::operator=(Pimpl&& other) { | |
LOG(" Move assignment called"); | |
if(&other != this) { | |
assert(this->pimpl_); | |
assert(other.pimpl_); | |
*this->pimpl_ = std::move(*other.pimpl_); | |
} | |
return *this; | |
} | |
Pimpl::PimplImpl& Pimpl::PimplImpl::operator=(PimplImpl&& other) { | |
LOG(" Implementation move assignment called"); | |
if(&other != this) { | |
this->map_ = std::move(other.map_); | |
} | |
return *this; | |
} | |
#endif | |
/////////////////////////////////////////////////////////////////////////////// | |
size_t Pimpl::Size() const noexcept { | |
assert(pimpl_); | |
return pimpl_->Size(); | |
} | |
size_t Pimpl::PimplImpl::Size() const noexcept { | |
return map_.size(); | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
void Pimpl::Clear() noexcept { | |
assert(pimpl_); | |
pimpl_->Clear(); | |
} | |
void Pimpl::PimplImpl::Clear() noexcept { | |
map_.clear(); | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
constexpr const char * const Pimpl::TypeName() { | |
return "Pimpl"; | |
} | |
constexpr const char * const Pimpl::PimplImpl::TypeName() { | |
return "PimplImpl"; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
void swap(Pimpl& lhs, Pimpl& rhs) { | |
using std::swap; | |
swap(lhs.pimpl_, rhs.pimpl_); | |
} | |
void swap(Pimpl::PimplImpl& lhs, | |
Pimpl::PimplImpl& rhs) { | |
using std::swap; | |
swap(lhs.map_, rhs.map_); | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
const Pimpl::MapValue& Pimpl::operator[](const MapKey& key) const { | |
assert(pimpl_); | |
return (*(this->pimpl_))[key]; | |
} | |
const Pimpl::PimplImpl::MapValue& | |
Pimpl::PimplImpl::operator[](const MapKey& key) const { | |
const auto it = map_.find(key); | |
if (it == map_.end()) { | |
std::stringstream ss; | |
ss << key; | |
throw std::invalid_argument(ss.str()); | |
} | |
return it->second; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
std::ostream& operator<<(std::ostream& stream, const Pimpl& object) { | |
assert(object.pimpl_); | |
return stream << *object.pimpl_; | |
} | |
std::ostream& operator<<(std::ostream& stream, | |
const Pimpl::PimplImpl& object) { | |
return stream << object.map_.size(); | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
typedef std::chrono::high_resolution_clock Clock; | |
typedef std::chrono::time_point<Clock> TimePoint; | |
typedef std::chrono::nanoseconds Nanoseconds; | |
/////////////////////////////////////////////////////////////////////////////// | |
static Nanoseconds PrintTime(const char * const msg, const TimePoint& start, | |
const TimePoint& end, const size_t iterations = 1) { | |
static size_t s_count = 0; | |
using std::cout; | |
using std::endl; | |
using std::chrono::duration_cast; | |
Nanoseconds ns = duration_cast<Nanoseconds>(end - start); | |
cout << " " << msg << ": " << (ns.count()/iterations) << "ns" << endl; | |
return ns; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
template<class C> | |
static Nanoseconds TestRunner() { | |
std::cout << "Testing: " << C::TypeName() << std::endl; | |
// Initialize | |
typename C::Map map { | |
{"some", "dummy"}, | |
{"data", "to"}, | |
{"fill", "up"}, | |
{"the", "map"} | |
}; | |
const size_t map_size = map.size(); | |
const size_t iterations = 500; | |
Nanoseconds ns; | |
// Assert the classes do what they are supposed to do | |
TimePoint start = std::chrono::high_resolution_clock::now(); | |
LOG(" map size(): " << map.size()); | |
LOG(" Constructing"); | |
C test(std::move(map)); | |
LOG(" map.size(): " << map.size()); | |
LOG(" test.Size(): " << test.Size()); | |
assert(map.size() == 0); | |
assert(test.Size() == map_size); | |
LOG(" Copy construction"); | |
C copy(test); | |
LOG(" copy.Size(): " << copy.Size()); | |
assert(copy.Size() == test.Size()); | |
LOG(" Move construction"); | |
C move(std::move(copy)); | |
LOG(" move.Size(): " << move.Size()); | |
LOG(" copy.Size(): " << copy.Size()); | |
assert(copy.Size() == 0); | |
assert(move.Size() == test.Size()); | |
LOG(" Swapping"); | |
using std::swap; | |
swap(move, copy); | |
LOG(" move.Size(): " << move.Size()); | |
LOG(" copy.Size(): " << copy.Size()); | |
assert(move.Size() == 0); | |
assert(copy.Size() == test.Size()); | |
LOG(" Swapping back"); | |
swap(move, copy); | |
LOG(" move.Size(): " << move.Size()); | |
LOG(" copy.Size(): " << copy.Size()); | |
assert(copy.Size() == 0); | |
assert(move.Size() == test.Size()); | |
LOG(" Copy assignment"); | |
copy = test; | |
LOG(" test.Size(): " << test.Size()); | |
LOG(" copy.Size(): " << copy.Size()); | |
assert(copy.Size() == test.Size()); | |
LOG(" Move assignment"); | |
move = std::move(copy); | |
LOG(" move.Size(): " << move.Size()); | |
LOG(" copy.Size(): " << copy.Size()); | |
assert(copy.Size() == 0); | |
assert(move.Size() == test.Size()); | |
LOG(" operator[]"); | |
typename C::MapValue value = test["some"]; | |
LOG(" some: " << value); | |
assert(value == "dummy"); | |
TimePoint end = std::chrono::high_resolution_clock::now(); | |
ns += PrintTime("Assertions", start, end); | |
// Time the default constructor | |
start = std::chrono::high_resolution_clock::now(); | |
for (size_t i = iterations; i; --i) { | |
C(); | |
} | |
end = std::chrono::high_resolution_clock::now(); | |
ns += PrintTime("Default Contruction", start, end, iterations); | |
// Time the copy constructor | |
start = std::chrono::high_resolution_clock::now(); | |
for (size_t i = iterations; i; --i) { | |
C(test); | |
} | |
end = std::chrono::high_resolution_clock::now(); | |
ns += PrintTime("Copy Contruction", start, end, iterations); | |
// Time the move constructor | |
start = std::chrono::high_resolution_clock::now(); | |
for (size_t i = iterations; i; --i) { | |
C moved(C(typename C::Map{{"single", "map"}})); | |
} | |
end = std::chrono::high_resolution_clock::now(); | |
ns += PrintTime("Move Contruction", start, end, iterations); | |
// Time the [] operator | |
start = std::chrono::high_resolution_clock::now(); | |
for (size_t i = iterations; i; --i) { | |
C moved(C(typename C::Map{{"single", "map"}})); | |
} | |
end = std::chrono::high_resolution_clock::now(); | |
ns += PrintTime("operator[]", start, end, iterations); | |
std::cout << C::TypeName() << " total time: " << ns.count() << "ns" << std::endl; | |
return ns; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
int main (const int argc, const char * const * const argv) { | |
const Nanoseconds nsp = TestRunner<Pimpl>(); | |
#ifdef MAKE_IMPLEMENTATION_PUBLIC | |
const Nanoseconds nspi = TestRunner<Pimpl::PimplImpl>(); | |
const double ldp = static_cast<long double>(nsp.count()); | |
const double ldpi = static_cast<long double>(nspi.count()); | |
std::cout << "The private implementation class is " << ldp/ldpi | |
<< " slower than the normal class" << std::endl; | |
#endif | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment