Skip to content

Instantly share code, notes, and snippets.

@mattyclarkson
Created January 24, 2013 17:08
Show Gist options
  • Save mattyclarkson/4625200 to your computer and use it in GitHub Desktop.
Save mattyclarkson/4625200 to your computer and use it in GitHub Desktop.
Are the benefits of private implementations worth the performance trade offs.
#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