Skip to content

Instantly share code, notes, and snippets.

@gintenlabo
Last active September 24, 2015 00:37
Show Gist options
  • Save gintenlabo/640298 to your computer and use it in GitHub Desktop.
Save gintenlabo/640298 to your computer and use it in GitHub Desktop.
pImpl イディオム + thin template 技法の簡単な例
#include "Stack.hpp"
#include <vector>
// Impl_ の実装(今回はメンバのみ)
// 複雑なクラスの場合は Impl_ 内にメンバ関数を定義するのもアリ
struct StackImpl_::Impl_
{
std::vector< boost::shared_ptr<void> > v;
};
// StackImpl_ のメンバ関数の実装
StackImpl_::StackImpl_()
: pImpl_( new Impl_() ) {}
StackImpl_::~StackImpl_() /*noexcept*/ {}
void StackImpl_::swap( StackImpl_& other ) /*noexcept*/ {
pImpl_->v.swap( other.pImpl_->v );
}
void StackImpl_::push( boost::shared_ptr<void> const& p ) {
pImpl_->v.push_back( p );
}
void* StackImpl_::top() const /*noexcept*/ {
return pImpl_->v.back().get();
}
void StackImpl_::pop() {
pImpl_->v.pop_back();
}
std::size_t StackImpl_::size() const /*noexcept*/ {
return pImpl_->v.size();
}
bool StackImpl_::empty() const /*noexcept*/ {
return pImpl_->v.empty();
}
void* StackImpl_::operator[]( std::size_t i ) const /*noexcept*/ {
return pImpl_->v[i].get();
}
void StackImpl_::reserve( std::size_t n ) {
return pImpl_->v.reserve(n);
}
#ifndef INCLUDED_STACK_HPP_
#define INCLUDED_STACK_HPP_
// pImpl なテンプレートクラス
#include <cstddef>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/scoped_ptr.hpp>
// #include <utility> // move を使うなら
// 実装用クラス(これを pImpl にする)
// 実際には Stack<T> からしかアクセスできないよう、
// 全て private にして Stack<T> を friend 宣言したほうがいい
struct StackImpl_
{
StackImpl_();
~StackImpl_() /*noexcept*/;
void swap( StackImpl_& other ) /*noexcept*/;
// move
// StackImpl_( StackImpl_&& src ) /*noexcept*/;
void push( boost::shared_ptr<void> const& );
void* top() const /*noexcept*/;
void pop();
std::size_t size() const /*noexcept*/;
bool empty() const /*noexcept*/;
void* operator[]( std::size_t i ) const /*noexcept*/;
void reserve( std::size_t n );
private:
class Impl_;
boost::scoped_ptr<Impl_> pImpl_;
};
// 実際に使うのは実装用クラスの薄いラッパ(thin wrapper)
template<class T>
struct Stack
{
Stack() {}
// ただし copy は(値の semantic を採用した場合)薄いラッパでは無理
Stack( Stack const& src )
{
// 深いコピーをする
std::size_t const n = src.size();
StackImpl_ const& src_ = src.impl_;
impl_.reserve( n );
for( std::size_t i = 0; i < n; ++i ) {
this->push( *static_cast<T const*>( src_[i] ) );
}
}
// move
// Stack( Stack && src ) /*noexcept*/
// : impl_( std::move( src.impl_ ) ) {}
// swap
void swap( Stack& other ) /*noexcept*/ {
impl_.swap( other.impl_ );
}
friend void swap( Stack& one, Stack& another ) /*noexcept*/ {
one.swap( another );
}
// operator=
Stack& operator=( Stack const& rhs ) {
Stack( rhs ).swap( *this );
return *this;
}
// Stack& operator=( Stack&& rhs ) /*noexcept*/ {
// this->swap( rhs );
// return *this;
// }
// 要素を push する
void push( T const& x ){
impl_.push( boost::make_shared<T>(x) );
}
// スタックトップの要素を得る
T& top() /*noexcept*/ {
return *static_cast<T*>( impl_.top() );
}
T const& top() const /*noexcept*/ {
return *static_cast<T const*>( impl_.top() );
}
// 要素を pop する
void pop() {
impl_.pop();
}
// 大きさを得る
std::size_t size() const /*noexcept*/ {
return impl_.size();
}
// 空かどうかチェックする
bool empty() const /*noexcept*/ {
return impl_.empty();
}
private:
StackImpl_ impl_;
};
#endif // #ifndef INCLUDED_STACK_HPP_
#include "Stack.hpp"
// とりあえず全メンバを明示的に実体化させておく
// 組み込み型
template class Stack<int>;
#include <string>
#include <cassert>
// 組み込みじゃない型
struct hoge
{
// 構築とか
hoge( std::string const& x_ = std::string() )
: x( x_ ) { ++instances_(); }
hoge( hoge const& src )
: x( src.x ) { ++instances_(); }
~hoge(){ --instances_(); }
// 現存するインスタンスの総数を返す
static int instances() {
int const result = instances_();
assert( result >= 0 );
return instances_();
}
// 適当にメンバを、せっかくなので非PODで
std::string x;
private:
// インスタンスの総数
static int& instances_() {
static int counter = 0;
return counter;
}
};
template class Stack<hoge>;
#include <boost/test/minimal.hpp>
int test_main( int, char** )
{
Stack<int> stack;
BOOST_CHECK( stack.empty() );
BOOST_CHECK( stack.size() == 0 );
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
BOOST_CHECK( stack.size() == 5 );
BOOST_CHECK( stack.top() == 5 );
// コピーテスト
{
// 空の Stack のコピー
{
Stack<int> orig;
Stack<int> copied = orig;
BOOST_CHECK( orig.size() == 0 && copied.size() == 0 );
// コピー元と無関係なことを確認
copied.push(0);
BOOST_CHECK( orig.size() == 0 && copied.size() == 1 && copied.top() == 0 );
orig.push(23);
BOOST_CHECK( orig.size() == 1 && orig.top() == 23 &&
copied.size() == 1 && copied.top() == 0 );
copied.pop();
BOOST_CHECK( orig.size() == 1 && orig.top() == 23 && copied.size() == 0 );
}
// 値のある Stack のコピー
Stack<int> const& orig = stack;
std::size_t const n = orig.size();
int const top = orig.top();
// copy test
Stack<int> copied = orig;
BOOST_CHECK( copied.size() == n && copied.top() == top );
// コピーしたスタックの先頭を書き換えても
// オリジナルに影響しないことを確認
copied.top() = 42;
BOOST_CHECK( orig.top() == top );
// pop してもおk?
copied.pop();
BOOST_CHECK( orig.size() == n );
// 順々に取り除いていく
BOOST_CHECK( copied.size() == 4 );
BOOST_CHECK( copied.top() == 4 );
copied.pop();
BOOST_CHECK( copied.size() == 3 );
BOOST_CHECK( copied.top() == 3 );
copied.pop();
BOOST_CHECK( copied.size() == 2 );
BOOST_CHECK( copied.top() == 2 );
copied.pop();
BOOST_CHECK( copied.size() == 1 );
BOOST_CHECK( copied.top() == 1 );
copied.pop();
// 空っぽ。
BOOST_CHECK( copied.empty() );
// オリジナルは変化なし
BOOST_CHECK( orig.size() == n );
BOOST_CHECK( orig.top() == top );
}
// 代入
{
Stack<int> temp;
temp = stack;
BOOST_CHECK( temp.size() == stack.size() && temp.top() == stack.top() );
// clear
temp = Stack<int>(); // あるいは Stack<int>().swap( temp );
BOOST_CHECK( temp.empty() );
// swap
temp.swap( stack );
BOOST_CHECK( !temp.empty() && stack.empty() );
swap( temp, stack );
BOOST_CHECK( temp.empty() && !stack.empty() );
}
// 操作(コピー先をいじってるけど、もう一度)
stack.push(6);
BOOST_CHECK( stack.size() == 6 );
BOOST_CHECK( stack.top() == 6 );
stack.pop();
BOOST_CHECK( stack.size() == 5 );
BOOST_CHECK( stack.top() == 5 );
stack.pop();
BOOST_CHECK( stack.size() == 4 );
BOOST_CHECK( stack.top() == 4 );
stack.pop();
BOOST_CHECK( stack.size() == 3 );
BOOST_CHECK( stack.top() == 3 );
stack.pop();
BOOST_CHECK( stack.size() == 2 );
BOOST_CHECK( stack.top() == 2 );
stack.pop();
BOOST_CHECK( stack.size() == 1 );
BOOST_CHECK( stack.top() == 1 );
stack.pop();
BOOST_CHECK( stack.size() == 0 );
BOOST_CHECK( stack.empty() );
// 大量に push
std::size_t const n = 100000;
for( std::size_t i = 0; i < n; ++i ) {
stack.push(i);
}
BOOST_CHECK( stack.size() == n );
for( int i = n - 1; i >= 0; --i ) {
BOOST_CHECK( stack.top() == i );
stack.pop();
}
BOOST_CHECK( stack.empty() );
// hoge についても適当に。
{
// 今までのインスタンスの総数
int const init_instances = hoge::instances();
// 一応ゼロであるはずなのでチェックしておく(なくてもいい)
BOOST_CHECK( init_instances == 0 );
// 空のスタックを構築
Stack<hoge> stack;
// インスタンス数は変化しない
BOOST_CHECK( hoge::instances() == init_instances );
// push
stack.push( hoge("foo") );
// ひとつ増える
BOOST_CHECK( hoge::instances() == init_instances + 1 );
BOOST_CHECK( stack.top().x == "foo" );
// コピー
{
Stack<hoge> temp = stack;
// もひとつ増える
BOOST_CHECK( hoge::instances() == init_instances + 2 );
BOOST_CHECK( temp.top().x == "foo" );
}
// 破棄されてインスタンス数は減少する
BOOST_CHECK( hoge::instances() == init_instances + 1 );
// さらにpop
stack.pop();
BOOST_CHECK( hoge::instances() == init_instances );
// 空になりました
BOOST_CHECK( stack.empty() );
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment