TinyXML + babelを使ってみる。(3) -シリアライズ-
前回(TinyXML + babelを使ってみる。(2) -Visitorでトラバース- - while( c++ );)の続き。
今回はシリアライズ、デシリアライズについて勉強します。
といっても、XMLファイルへの出力にはまだ触れていないので、デシリアライズのみ扱います。
以下のようなXMLとクラスがある時、
<?xml version="1.0" ?> <test> <hoge a="1" b="2.0" c="ほげほげ" /> </test>
class Hoge { private: int a; float b; std::string c; };
ISerializableインターフェイスを実装し、属性(Attribute)からHogeクラスのデシリアライズを行います。
デシリアライズ時に直接XMLの操作をしたくないですし、XML以外のファイルからのデシリアライズも考慮して、IArchiverインターフェイスを使って間接的に属性を取得します。
class ISerializable { public: //virtual void serialize( IArchiver* ar ) = 0; virtual void deserialize( IArchiver* ar ) = 0; };
class IArchiver { public: virtual bool get_int( const std::string& name, int& value )const = 0; virtual bool get_float( const std::string& name, float& value )const = 0; virtual bool get_string( const std::string& name, std::string& value )const = 0; virtual void set( const std::string& name, const std::string& value ) = 0; };
class Hoge : public ISerializable { public: void deserialize( IArchiver* ar ) { ar->get_int( "a", a ); ar->get_float( "b", b ); ar->get_string( "c", c ); } private: int a; float b; std::string c; };
IArchiver実装クラス
とりあえず、属性リストをstd::mapでテーブル化しておきます。
class Archiver : public IArchiver { typedef std::map< std::string, std::string > table_t; public: bool get_int( const std::string& name, int& value )const { return get( name, value ); } bool get_float( const std::string& name, float& value )const { return get( name, value ); } bool get_string( const std::string& name, std::string& value )const { return get( name, value ); } void set( const std::string& name, const std::string& value ) { table[ name ] = value; } private: template< typename T > bool get( const std::string& name, T& value ) { table_t::iterator it = table.find( name ); if( it != table.end() ) { std::stringstream ss( it->second ); ss >> value; return true; } return false; } private: table_t table; };
Hogeクラス
Archiverの属性テーブルから値を取得してメンバに設定します。
class Hoge : public ISerializable { public: void deserialize( IArchiver* ar ) { ar->get_int( "a", a ); ar->get_float( "b", b ); ar->get_string( "c", c ); } public: void func() { std::cout << "a=" << a << std::endl << "b=" << b << std::endl << "c=" << c << std::endl ; } private: int a; float b; std::string c; };
デシリアライズ用Visitor
hoge要素を訪れた際に属性テーブルを生成し、Hogeクラスのデシリアライズを行います。
class Visitor : public TiXmlVisitor { public: /// Visit an element. virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ) { std::string name = element.ValueStr(); //属性テーブルの作成 Archiver ar; create_attribute_table( firstAttribute, &ar ); //Hogeのでシリアライズ if( name == "hoge" ) { hoge->deserialize( &ar ); } return true; } private: void create_attribute_table( const TiXmlAttribute* a, IArchiver* ar ) { while( a ) { ar->set( a->Name(), babel::utf8_to_sjis( a->ValueStr() ) ); a = a->Next(); } } public: Visitor( Hoge* hoge ) : hoge( hoge ) {} private: Hoge* hoge; };
使ってみる
int main() { babel::init_babel(); Hoge hoge; TiXmlDocument doc( XMLファイルを指定 ); if( doc.LoadFile() ) { Visitor visitor( &hoge ); doc.Accept( &visitor ); } hoge.func(); return 0; }
実行結果
a=1 b=2 c=ほげほげ
テキストノードの取得
以下のようにテキストノードとメンバの文字列を対応させるには、親要素でテキストを取得してテキストノードへのVisitを行わないように、Visitメソッドでfalseを返します。
<?xml version="1.0" ?> <test> <hoge a="1" b="2.0" c="ほげほげ" /> <piyo>ぴよぴよ</piyo> </test>
class Piyo { std::string text; };
class Visitor : public TiXmlVisitor { public: /// Visit an element. virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ) { std::string name = element.ValueStr(); //属性テーブルの作成 Archiver ar; create_attribute_table( firstAttribute, &ar ); //各オブジェクトのデシリアライズ if( name == "hoge" ) { hoge->deserialize( &ar ); } if( name == "piyo" ) { //テキストノードの取得 get_text( element.FirstChild(), &ar ); piyo->deserialize( &ar ); return false; //子ノードのVisitを行わない } return true; } void get_text( const TiXmlNode* text, IArchiver* ar ) { if( text != 0 && text->Type() == TiXmlNode::TEXT ) { ar->set_text( babel::utf8_to_sjis( text->ValueStr() ) ); } } ... };
IArchiverインターフェイス
とりあえずテキストノードの取得、設定が出来るようにメソッドを追加しておきます。
class IArchiver { public: ... virtual std::string get_text()const = 0; virtual void set_text( const std::string& text ) = 0; };
IArchiver実装クラス
class Archiver : public IArchiver { public: ... std::string get_text()const { return text; } void set_text( const std::string& text ) { this->text = text; } private: std::string text; };
Piyoクラス
class Piyo : public ISerializable { public: void deserialize( IArchiver* ar ) { text = ar->get_text(); } public: void func() { std::cout << "text=" << text << std::endl; } public: Piyo() : text( "" ) {} private: std::string text; };
使ってみる
int main() { babel::init_babel(); Hoge hoge; Piyo piyo; TiXmlDocument doc( XMLファイルを指定 ); if( doc.LoadFile() ) { Visitor visitor( &hoge, &piyo ); doc.Accept( &visitor ); } hoge.func(); piyo.func(); return 0; }
実行結果
a=1 b=2 c=ほげほげ text=ぴよぴよ
サンプル
- tinyxml_03.zip
- VC++2008EEで動作確認
- tinyxmlを使用しています
- babelを使用しています
TinyXML + babelを使ってみる。(2) -Visitorでトラバース-
前回(TinyXML + babelを使ってみる。(1) - while( c++ );)の続き。
XMLファイルからDOMツリーを構築
TiXmlDocument doc(pFilename); bool loadOkay = doc.LoadFile(); if (loadOkay) { ... } else { //Failed to load file }
メモリからDOMツリーを構築
TiXmlDocument doc; const char* str = "<?xml version=\"1.0\" ?>" "<Hello>World</Hello>" ; doc.Parse( str ); bool loadOkay = !doc.Error(); if (loadOkay) { ... } else { //Failed to load file }
以上のように、XMLファイルまたはメモリからDOMツリーの構築が可能です。
前回はルートノードから再帰的にノードの情報を出力する関数を作りましたが(コピペ)、
今回はいわゆるVisitorパターンを使ってコンソールに出力してみます。
「Visitorさん」がノードを巡りながら「要素名、テキスト」を叫びます。
Visitorパターンに関してはGoogle先生に聞いてみて下さい。
TiXmlVisitor
TinyXMLにはDOMツリーをトラバースするためのVisitorクラスが用意されています。
class TiXmlVisitor { public: virtual ~TiXmlVisitor() {} /// Visit a document. virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { return true; } /// Visit a document. virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { return true; } /// Visit an element. virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ ) { return true; } /// Visit an element. virtual bool VisitExit( const TiXmlElement& /*element*/ ) { return true; } /// Visit a declaration virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; } /// Visit a text node virtual bool Visit( const TiXmlText& /*text*/ ) { return true; } /// Visit a comment node virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; } /// Visit an unknow node virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; } };
DOMツリーは6種類のノードで構成されています。
- TiXmlNode
- TiXmlDocument
- TiXmlElement
- TiXmlDeclaration
- TiXmlText
- TiXmlComment
- TiXmlUnknown
「要素名」と「テキスト」を出力するためには、以下の2つメソッドをオーバーライドしたVisitorクラスを作ります。
- bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ );
- bool Visit( const TiXmlText& /*text*/ );
class Visitor : public TiXmlVisitor { public: /// Visit an element. virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* /*firstAttribute*/ ) { std::cout << element.ValueStr() << std::endl; return true; } /// Visit a text node virtual bool Visit( const TiXmlText& text ) { std::cout << babel::utf8_to_sjis( text.ValueStr() ) << std::endl; return true; } };
各ノードはVisitorを受け入れるためのAcceptメソッドを実装しているので、ルートのTiXmlDocumentからAcceptを呼び出せば、自動的に全てのノードを巡りつつ、オーバーライドしたメソッドを呼び出してくれます。
int main() { babel::init_babel(); TiXmlDocument doc( "test.xml" ); if( doc.LoadFile() ) { Visitor visitor; doc.Accept( &visitor ); } return 0; }
実行結果
test hoge piyo ぴよぴよ
さらに前回と同様の結果になるよう修正してみます。
class Visitor : public TiXmlVisitor { private: enum{ NUM_INDENTS_PER_SPACE = 2 }; const char * getIndent( unsigned int numIndents ) { static const char * pINDENT=" + "; static const unsigned int LENGTH=strlen( pINDENT ); unsigned int n=numIndents*NUM_INDENTS_PER_SPACE; if ( n > LENGTH ) n = LENGTH; return &pINDENT[ LENGTH-n ]; } // same as getIndent but no "+" at the end const char * getIndentAlt( unsigned int numIndents ) { static const char * pINDENT=" "; static const unsigned int LENGTH=strlen( pINDENT ); unsigned int n=numIndents*NUM_INDENTS_PER_SPACE; if ( n > LENGTH ) n = LENGTH; return &pINDENT[ LENGTH-n ]; } public: /// Visit a document. virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { std::cout << "Document" << std::endl; indent ++; return true; } /// Visit a document. virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { indent --; return true; } /// Visit an element. virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ) { std::cout << getIndent( indent ); std::cout << "Element: [" << element.ValueStr() << "]" << std::endl; int count = visit_attributes( firstAttribute ); if( count == 0 ) std::cout << " (No attributes)"; else { // "1 attribute" std::cout << getIndentAlt(indent) << count << " attribute"; // "n attributes" if( count > 1 )std::cout << "s"; } std::cout << std::endl; indent ++; return true; } /// Visit an element. virtual bool VisitExit( const TiXmlElement& /*element*/ ) { indent --; return true; } /// Visit a declaration virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { std::cout << getIndent( indent ); std::cout << "Declaration" << std::endl; return true; } /// Visit a text node virtual bool Visit( const TiXmlText& text ) { std::cout << getIndent( indent ); std::cout << "Text: [" << babel::utf8_to_sjis( text.ValueStr() ) << "]" << std::endl; return true; } /// Visit a comment node virtual bool Visit( const TiXmlComment& comment ) { std::cout << getIndent( indent ); std::cout << "Comment: [" << comment.ValueStr() << "]" << std::endl; return true; } /// Visit an unknow node virtual bool Visit( const TiXmlUnknown& unknown ) { std::cout << getIndent( indent ); std::cout << "Unknown: [" << unknown.ValueStr() << "]" << std::endl; return true; } private: int visit_attributes( const TiXmlAttribute* a ) { int count = 0; while( a ) { std::cout << getIndent( indent + 1 ); std::cout << a->Name() << ": " << babel::utf8_to_sjis( a->ValueStr() ); int ival = 0; double dval = 0; if( a->QueryIntValue( &ival ) == TIXML_SUCCESS ) std::cout << " int=" << ival; if( a->QueryDoubleValue( &dval ) == TIXML_SUCCESS ) std::cout << " double=" << dval; std::cout << std::endl; a = a->Next(); count ++; } return count; } public: Visitor() : indent( 0 ) {} private: int indent; };
実行結果
Document + Declaration + Element: [test] (No attributes) + Element: [hoge] + a: 1 int=1 double=1 + b: 2.0 int=2 double=2 + c: ほげほげ 3 attributes + Element: [piyo] (No attributes) + Text: [ぴよぴよ]
サンプル
- tinyxml_01.zip
- VC++2008EEで動作確認。
- tinyxmlとbabelを使っています。
- tinyxmlではTIXML_USE_STLを指定してstd::stringを使っています。
winsockを使ってみる。(2) -acceptスレッド-
前回(winsockを使ってみる。(1) - while( c++ );)、コネクションを作って簡単な送受信を行いましたが、acceptはいわゆるブロッキング関数であり、クライアントが接続してくるまで、またはサーバソケットが閉じられるまで、呼び出し元スレッドが停止します。このままでは複数のクライアントと通信する場合やサーバ側で何らかの処理を行う場合に不都合なので、サブスレッドでacceptを行います。
- メインスレッドからacceptスレッドを作成
- acceptスレッドでクライアントのconnect待ち
- とりあえずコネクション確立時に文字列を送り返す
- サーバソケットを閉じるとacceptスレッド終了
hogeテンプレートライブラリ
- smart_ptr
- shared_ptr
- weak_ptr
- intrusive_ptr
- scoped_ptr
- nullptr
- enable_shared_from_this
- thread
- thread
- event
Server
/** * acceptスレッド実行関数 */ struct AcceptThreadFunc { void operator()() { for( ; ; ) { //クライアントの接続待ち std::cout << "クライアントの接続待ち" << std::endl; SocketPtr client = socket->accept(); if( client == nullptr )break; //ソケットからストリームを取得 SocketStreamWeakPtr stream = client->get_stream(); //適当な文字列を送る stream->write_line( "hoge" ); } } AcceptThreadFunc( const ServerSocketPtr& socket ) : socket( socket ) {} ServerSocketPtr socket; };
#include <net/socket.h> #include <hoge/thread/thread.h> int main() { try { //winsock初期化 net::startup(); //指定ポートでサーバ用TCPソケットを作成 ServerSocketPtr socket = IServerSocket::create( 12345 ); //acceptスレッド開始 hoge::thread_ptr accept_thread = hoge::thread::create( AcceptThreadFunc( socket ) ); accept_thread->start(); for( ; ; ) { if( GetAsyncKeyState( VK_ESCAPE ) & 0x8000 )break; } //ソケットを閉じて、acceptスレッドを終了させる socket->close(); //acceptスレッドの終了待ち accept_thread->join(); } catch( net::socket_error& e ) { std::cout << e.what() << std::endl; } //winsock破棄 net::cleanup(); return 0; }
- ISocketにcloseメソッドを追加した。
- 例外発生時にacceptスレッドが正常終了しないですが、とりあえず気にしない。
- Escキーで終了。
Client
#include <net/socket.h> int main() { try { using namespace net::tcp; //winsock初期化 net::startup(); //クライアント用TCPソケットを作成 ClientSocketPtr socket = IClientSocket::create(); //指定ポート、IPアドレスのサーバに接続 socket->connect( 12345, "localhost" ); //ソケットからストリームを取得 SocketStreamWeakPtr stream = socket->get_stream(); //1行分の文字列を受信 std::string str; stream->read_line( str ); std::cout << str; } catch( net::socket_error& e ) { std::cout << e.what() << std::endl; } net::cleanup(); return 0; }
サンプル
- winsock_02.zip
- VC++2008EEで動作確認
winsockを使ってみる。(1)
boost::asioを使いたかったのですが、ネットワークの知識が乏しいので、まずはwinsockの勉強をしようと思います。
Server
- 接続してきたクライアントに文字列を送り返す。
- SocketとStreamはsmart_ptrで。
#include <net/socket.h> int main() { try { using namespace net::tcp; //winsock初期化 net::startup(); //指定ポートでサーバ用TCPソケットを作成 ServerSocketPtr socket = IServerSocket::create( 12345 ); //クライアントの接続待ち SocketPtr client = socket->accept(); //ソケットからストリームを取得 SocketStreamWeakPtr stream = client->get_stream(); //適当な文字列を送る stream->write_line( "hoge" ); } catch( net::socket_error& e ) { std::cout << e.what() << std::endl; } //winsock破棄 net::cleanup(); return 0; }
Client
- サーバに接続して文字列を受信
- SocketとStreamはsmart_ptrで。
#include <net/socket.h> int main() { try { using namespace net::tcp; //winsock初期化 net::startup(); //クライアント用TCPソケットを作成 ClientSocketPtr socket = IClientSocket::create(); //指定ポート、IPアドレスのサーバに接続 socket->connect( 12345, "localhost" ); //ソケットからストリームを取得 SocketStreamWeakPtr stream = socket->get_stream(); //1行分の文字列を受信 std::string str; stream->read_line( str ); std::cout << str; } catch( net::socket_error& e ) { std::cout << e.what() << std::endl; } //winsock破棄 net::cleanup(); return 0; }
winsockの初期化と破棄
- socket_error
- WSAGetLastErrorの戻り値を文字列に変換
- startup
- winsockの初期化
- cleanup
- winsockの破棄
socket.h
#include <winsock2.h> #pragma comment( lib, "wsock32.lib" ) #include <string> #include <sstream> #include <iostream> #include <exception> namespace net { /** * ソケット例外クラス */ class socket_error : public std::exception { public: socket_error() { //エラーコードを文字列化 void* msg; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, WSAGetLastError(), MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), ( char* )&msg, 0, 0 ); str = static_cast< const char* >( msg ); LocalFree( msg ); } socket_error( const std::string& str ) : str( str ) { } public: const char * __CLR_OR_THIS_CALL what() const { return str.c_str(); } private: std::string str; }; } namespace net { /** * winsockの初期化 */ void startup(); /** * winsockの破棄 */ void cleanup(); }
socket.cpp
namespace net { /** * WinSock初期化 */ void startup() { WORD version = MAKEWORD( 2, 0 ); WSADATA data = { 0 }; int err = WSAStartup( version, &data ); //初期化失敗 if( err != 0 ) throw socket_error(); //指定したバージョンと異なる if( data.wVersion != version ) throw socket_error( "指定したバージョンと異なる" ); //WSADATAの確認 std::stringstream ss; ss << data.wVersion << std::endl << data.iMaxSockets << std::endl << data.iMaxUdpDg << std::endl //<< data.lpVendorInfo << std::endl << data.szDescription << std::endl << data.szSystemStatus << std::endl << data.wHighVersion << std::endl << data.wVersion << std::endl ; std::cout << ss.str(); } /** * WinSock破棄 */ void cleanup() { WSACleanup(); } }
参照カウンタクラス
ソケットクラスのインスタンスは、侵入型参照カウンタとintrusive_ptrで管理してみます。
参照カウンタの実装はこちらを参考にしました。
socket.h
namespace net { /** * intrusive_ptr用参照カウンタ */ class RefCounter { public: RefCounter& operator++(){ ++ count; return *this; } RefCounter& operator--(){ -- count; return *this; } operator int()const{ return count; } public: RefCounter() : count( 0 ){} private: int count; }; } /** * 参照カウンタ用インターフェイスの宣言 */ #define declare_ref_counter( name ) \ private: \ virtual void add_ref() = 0; \ virtual void release() = 0; \ friend void intrusive_ptr_add_ref( name* p ){ p->add_ref(); } \ friend void intrusive_ptr_release( name* p ){ p->release(); } /** * 参照カウンタ用インターフェイスの実装 */ #define implement_ref_counter( name ) \ private: \ RefCounter ref_counter; \ private: \ void add_ref(){ ++ ref_counter; } \ void release(){ if( -- ref_counter == 0 )delete this; }
Socketインターフェイス
- ISocket
- add_ref
- release
socket.h
namespace net { namespace tcp { /** * Socketインターフェイス */ class ISocket { declare_ref_counter( ISocket ); protected: virtual ~ISocket(){} }; } }
- SocketBase
- Socketを作成する
- 既存のSOCKET、sockaddr_inからSocketを作成する
- Socketを閉じる
socket.cpp
namespace net { namespace tcp { /** * TCP Socket */ class SocketBase { public: void close() { if( s != INVALID_SOCKET ) { ::shutdown( s, SD_BOTH ); ::closesocket( s ); s = INVALID_SOCKET; } } public: SocketBase() : s( ::socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) { memset( &sa, 0, sizeof( sa ) ); } SocketBase( SOCKET s, const sockaddr_in& sa ) : s( s ) , sa( sa ) { } virtual ~SocketBase() { close(); } protected: SOCKET s; sockaddr_in sa; }; } }
- SocketImpl
- 参照カウンタの実装
namespace net { namespace tcp { /** * TCP Socketの実装クラス */ class SocketImpl : public SocketBase , public ISocket { implement_ref_counter( SocketImpl ); public: SocketImpl() : SocketBase() { } SocketImpl( SOCKET s, const sockaddr_in& sa ) : SocketBase( s, sa ) { } }; } }
SocketStreamインターフェイス
- ISocketStream
- add_ref
- release
- write_line
- 1行送信
- read_line
- 1行受信
socket.h
namespace net { namespace tcp { /** * SocketStreamインターフェイス */ class ISocketStream { public: virtual void write_line( const std::string& str ) = 0; virtual int read_line( std::string& str ) = 0; protected: virtual ~ISocketStream(){} }; } }
- SocketStreamImpl
- add_ref
- release
- write_line
- 引数で受けっとった文字列に改行コードを付加して送信
- read_line
- 改行コードまで1文字ずつ受信
socket.cpp
namespace net { namespace tcp { /** * SocketStream実装 */ class SocketStreamImpl : public ISocketStream { public: void write_line( const std::string& str ) { std::string s = str + "\n"; size_t size = s.size(); ::send( socket->s, s.c_str(), size, 0 ); } int read_line( std::string& str ) { int ret = 0; //改行コードまで1文字ずつ受信 while( bool b = true ) { char c; ret = ::recv( socket->s, &c, 1, 0 ); if( ret == 0 || ret == SOCKET_ERROR ) { //切断されたorエラー break; } else { str += c; if( c == '\n' ) break; } } return ret; } public: SocketStreamImpl( SocketBase* socket ) : socket( socket ) {} private: SocketBase* socket; }; } }
SocketインターフェイスからSocketStreamインターフェイスを取得する
SocketクラスがSocketStreamクラスを所有し、weak_ptrで使用します。
- ISocket
- get_streamを追加
socket.h
namespace net { namespace tcp { class ISocket; typedef hoge::intrusive_ptr< ISocket > SocketPtr; class ISocketStream; typedef hoge::shared_ptr< ISocketStream > SocketStreamPtr; typedef hoge::weak_ptr< ISocketStream > SocketStreamWeakPtr; } } namespace net { namespace tcp { /** * Socketインターフェイス */ class ISocket { ..略.. public: virtual SocketStreamWeakPtr get_stream() = 0; }; } }
socket.cpp
namespace net { namespace tcp { /** * TCP Socketの実装クラス */ class SocketImpl : public SocketBase , public ISocket { ..略.. public: SocketStreamWeakPtr get_stream() { return stream; } public: SocketImpl() : SocketBase() { stream = SocketStreamPtr( new SocketStreamImpl( this ) ); } SocketImpl( SOCKET s, const sockaddr_in& sa ) : SocketBase( s, sa ) { stream = SocketStreamPtr( new SocketStreamImpl( this ) ); } private: SocketStreamPtr stream; }; } }
ServerSocketインターフェイス
- IServerSocket
- add_ref
- release
- bind
- accept
socket.h
namespace net { namespace tcp { class IServerSocket; typedef hoge::intrusive_ptr< IServerSocket > ServerSocketPtr; } } namespace net { namespace tcp { /** * ServerSocketインターフェイス */ class IServerSocket { declare_ref_counter( IServerSocket ); public: virtual void bind( WORD port ) = 0; virtual SocketPtr accept() = 0; protected: virtual ~IServerSocket(){} public: static ServerSocketPtr create( WORD port ); }; } }
- ServerSocketImpl
- add_ref
- release
- bind
- ポートとソケットのバインド
- accept
- 接続を受け入れたクライアントのSocketImplを生成して返す
- create
- 指定ポートにバインドしたサーバソケットを生成して返す
socket.cpp
namespace net { namespace tcp { /** * ServerSocket実装 */ class ServerSocketImpl : public SocketBase , public IServerSocket { implement_ref_counter( ServerSocketImpl ); public: void bind( WORD port ) { sa.sin_family = AF_INET; sa.sin_port = htons( port ); sa.sin_addr.s_addr = htonl( INADDR_ANY ); // ポートとソケットのバインド int err = ::bind( s, ( sockaddr* )&sa, sizeof( sa ) ); if( err == SOCKET_ERROR ) throw socket_error(); // 接続待ち状態へ ::listen( s, SOMAXCONN ); } SocketPtr accept() { // クライアントの接続許可 sockaddr_in sa = { 0 }; int size = sizeof( sa ); SOCKET client = ::accept( s, ( sockaddr* )&sa, &size ); if( client == INVALID_SOCKET ) { return nullptr; } SocketPtr p( new SocketImpl( client, sa ) ); return p; } public: ServerSocketImpl() : SocketBase() { } }; ServerSocketPtr IServerSocket::create( WORD port ) { ServerSocketPtr p( new ServerSocketImpl() ); p->bind( port ); return p; } } }
ClientSocketインターフェイス
- IClientSocket
- add_ref
- release
- get_stream
- connect
socket.h
namespace net { namespace tcp { /** * ClientSocketインターフェイス */ class IClientSocket { declare_ref_counter( IClientSocket ); public: virtual SocketStreamWeakPtr get_stream() = 0; virtual void connect( WORD port, const std::string& ip_address ) = 0; protected: virtual ~IClientSocket(){} public: static ClientSocketPtr create(); static ClientSocketPtr create( WORD port, const std::string& ip_address ); }; } }
socket.cpp
namespace net { namespace tcp { /** * ClientSocket実装 */ class ClientSocketImpl : public SocketBase , public IClientSocket { implement_ref_counter( ClientSocketImpl ); public: SocketStreamWeakPtr get_stream() { return stream; } public: void connect( WORD port, const std::string& ip_address ) { // サーバへ接続要求 sa.sin_family = AF_INET; sa.sin_port = htons( port ); if( ip_address == "localhost" ) sa.sin_addr.s_addr = htonl( INADDR_LOOPBACK ); else sa.sin_addr.s_addr = inet_addr( ip_address.c_str() ); int err = ::connect( s, ( sockaddr* )&sa, sizeof( sa ) ); if( err == SOCKET_ERROR ) throw socket_error(); } public: ClientSocketImpl() : SocketBase() { stream = SocketStreamPtr( new SocketStreamImpl( this ) ); } private: SocketStreamPtr stream; }; ClientSocketPtr IClientSocket::create() { ClientSocketPtr p( new ClientSocketImpl() ); return p; } ClientSocketPtr IClientSocket::create( WORD port, const std::string& ip_address ) { ClientSocketPtr p( new ClientSocketImpl() ); p->connect( port, ip_address ); return p; } } }
使ってみる
サンプル
- winsock_01.zip
- VC++2008EEで動作確認
Server
#include <net/socket.h> int main() { try { using namespace net::tcp; //winsock初期化 net::startup(); //指定ポートでサーバ用TCPソケットを作成 ServerSocketPtr socket = IServerSocket::create( 12345 ); //クライアントの接続待ち std::cout << "クライアントの接続待ち" << std::endl; SocketPtr client = socket->accept(); //ソケットからストリームを取得 SocketStreamWeakPtr stream = client->get_stream(); //適当な文字列を送る stream->write_line( "hoge" ); } catch( net::socket_error& e ) { std::cout << e.what() << std::endl; } //winsock破棄 net::cleanup(); return 0; }
Client
#include <net/socket.h> int main() { try { using namespace net::tcp; //winsock初期化 net::startup(); //クライアント用TCPソケットを作成 ClientSocketPtr socket = IClientSocket::create(); //指定ポート、IPアドレスのサーバに接続 socket->connect( 12345, "localhost" ); //ソケットからストリームを取得 SocketStreamWeakPtr stream = socket->get_stream(); //1行分の文字列を受信 std::string str; stream->read_line( str ); std::cout << str; } catch( net::socket_error& e ) { std::cout << e.what() << std::endl; } net::cleanup(); return 0; }
次回から単純なチャットソフトを作ってみたいと思います。
shared_ptrの実装9 -weak_ptrの実装-
前回(shared_ptrの実装8 -weak_ptrに対応させる- - while( c++ );)の続き。
weak_ptrを作ります。
sp_counted_base
リソースを管理するための参照数(shared、weak)を保持するクラス。shared_count、weak_countによって共有される共有オブジェクト。
class sp_counted_base { public: sp_counted_base() : shared( 1 ) , weak( 1 ) {} virtual ~sp_counted_base(){} private: int shared; ///< shared_ptr用参照カウンタ int weak; ///< weak_ptr用参照カウンタ //コピー禁止 private: sp_counted_base( sp_counted_base const& ); sp_counted_base& operator=( sp_counted_base const& ); };
sharedの上げ下げ
- addref
- リソースがdeleteされていなければ、sharedのincrement。
- release
- shared==0になったらdispose()でリソースを破棄し、weak_release()でweakをdecrementする。
- dispose
- 派生クラスsp_counted_impl_p、sp_counted_impl_pdで実装。
class sp_counted_base { //shared public: bool addref() { if( shared == 0 )return false; shared ++; return true; } void release() { if( -- shared == 0 ) { dispose(); weak_release(); } } virtual void dispose() = 0; ..略.. };
weakの上げ下げ
- weak_addref
- weakのincrement
- weak_release
- weakをdecrementして0になったら自身をdeleteする。
class sp_counted_base { ..略.. //weak public: void weak_addref() { weak ++; } void weak_release() { if( -- weak == 0 ) { destroy(); } } void destroy() { delete this; } ..略.. };
sp_counted_impl
参照カウンタの上げ下げと、リソースの所有権を持つsp_counted_base派生クラス。
sp_counted_impl_p
リソースの保持とdeleteを行う。
template < typename T > class sp_counted_impl_p : public sp_counted_base { public: void dispose() { delete ptr; } public: sp_counted_impl_p( T* ptr ) : ptr( ptr ) {} private: T* ptr; };
sp_counted_impl_pd
custom deleterによるリソースのdelete。
template < typename T, typename D > class sp_counted_impl_pd : public sp_counted_base { public: void dispose() { del( ptr ); } public: sp_counted_impl_pd( T* ptr, D del ) : ptr( ptr ) , del( del ) {} private: T* ptr; D del; };
shared_count
weak_countからshared_countを生成するためのコンストラクタを追加。
class shared_count { friend class weak_count; private: sp_counted_base* impl; ..略.. public: explicit shared_count( weak_count const& c ); shared_count( weak_count const& c, no_throw_tag ); }; class weak_count{ ... }; inline shared_count::shared_count( weak_count const& c ) : impl( c.impl ) { if( impl == nullptr || !impl->addref() ) { throw bad_weak_ptr(); } } inline shared_count::shared_count( weak_count const& c, no_throw_tag ) : impl( c.impl ) { if( impl != nullptr && !impl->addref() ) { impl = nullptr; } }
weak_count
weak_ptrが保持する参照カウンタクラス。shared_ptrとsp_counted_base*を共有している。
shared_ptrが持つshared_countから生成される。
- コンストラクタ
- weak_addref
- デストラクタ
- weak_release。
- operator=
- コピー元sp_counted_baseのweak_release
- コピー先sp_counted_baseのweak_addref
class weak_count { friend class shared_count; private: sp_counted_base* impl; public: weak_count() : impl( nullptr ) {} weak_count( shared_count const& c ) : impl( c.impl ) { if( impl != nullptr ) impl->weak_addref(); } weak_count( weak_count const& c ) : impl( c.impl ) { if( impl != nullptr ) impl->weak_addref(); } ~weak_count() { if( impl != nullptr ) impl->weak_release(); } public: bool empty()const { return impl == nullptr; } public: weak_count& operator=( shared_count const& c ) { sp_counted_base* temp = c.impl; if( temp != impl ) { if( temp )temp->weak_addref(); if( impl )impl->weak_release(); impl = temp; } return *this; } weak_count& operator=( weak_count const& c ) { sp_counted_base* temp = c.impl; if( temp != impl ) { if( temp )temp->weak_addref(); if( impl )impl->weak_release(); impl = temp; } return *this; } };
weak_ptr
- lock
- リソースが既にdeleteされていればnullptrを指すshared_ptr、まだ生きていればsp_counted_baseを共有するshared_ptrを返す。
- operator*、operator->
template< typename Type > class weak_ptr { public: typedef typename shared_ptr_traits< Type >::reference_t reference_t; public: weak_ptr() : ptr( nullptr ) , count() {} template< typename Y > weak_ptr( weak_ptr< Y > const& p ) : ptr( p.lock().get() ) , count( p.count ) { } template< typename Y > weak_ptr( shared_ptr< Y > const& p ) : ptr( p.ptr ) , count( p.count ) { } public: template< typename Y > weak_ptr& operator=( weak_ptr< Y > const& p ) { ptr = p.lock().get(); count = p.count; return *this; } template< typename Y > weak_ptr& operator=( shared_ptr< Y > const& p ) { ptr = p.ptr; count = p.count; return *this; } public: shared_ptr< Type > lock()const { return shared_ptr< Type >( *this, no_throw_tag() ); } public: reference_t operator*()const { if( ptr == nullptr )throw nullptr_exception(); return *ptr; } Type* operator->()const { if( ptr == nullptr )throw nullptr_exception(); return ptr; } private: Type* ptr; weak_count count; };
test
#include <iostream> #include "smart_ptr/shared_ptr.h" #include "smart_ptr/weak_ptr.h" #include <boost/smart_ptr.hpp> class Hoge { public: void func(){ std::cout << "hoge" << std::endl; } }; int main() { //メモリリークの検出 _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); { hoge::weak_ptr< Hoge > q; { hoge::shared_ptr< Hoge > p( new Hoge() ); q = p; } if( hoge::shared_ptr< Hoge > temp = q.lock() ) temp->func(); } return 0; }
とりあえず、期待通りに動作してるようです。
足りない機能は今後追加予定。
shared_ptrの実装8 -weak_ptrに対応させる-
boost::weak_ptrの使い方は、多くの方々が解説していますので割愛します。
ここでは実装方法についてかなり適当に解説します。
一応前回(shared_ptrの実装7 - while( c++ );)の続き。
まとめページ(shared_ptrまとめ - while( c++ );)にshared_ptrのサンプルをアップしました。
とりあえず以下のよう扱い方が出来るweak_ptrを目指します。
使い方
class Hoge { public: void func(){} }; int main() { weak_ptr< Hoge > b; { shared_ptr< Hoge > a( new Hoge() ); b = a; } if( shared_ptr< Hoge > temp = b.lock() ) { temp->func(); //既にdelete済みなので実行されない } return 0; }
weak_ptrの仕組み
weak_ptrに対応させるためには、参照カウンタクラス(shared_count、weak_count)によって共有される共有オブジェクトクラス(sp_counted_base)に、shared_ptr/weak_ptr用の2つのカウンタを持たせます。
class sp_counted_base { ..略.. public: virtual void dispose() = 0; public: sp_counted_base() : shared( 1 ) , weak( 1 ) {} private: int shared; ///< shared_ptr用参照カウンタ int weak; ///< weak_ptr用参照カウンタ };
template < typename T > class sp_counted_impl_p : public sp_counted_base { public: void dispose() { delete ptr; } public: sp_counted_impl_p( T* ptr ) : ptr( ptr ) {} private: T* ptr; }; template < typename T, typename D > class sp_counted_impl_pd : public sp_counted_base { public: void dispose() { del( ptr ); } public: sp_counted_impl_pd( T* ptr, D del ) : ptr( ptr ) , del( del ) {} private: T* ptr; D del; };
使い方の例のように適当なクラスをshared_ptrに格納すると、デフォルトでsp_counted_impl_pのインスタンスが生成され、shared==1、weak==1となります。
class Hoge { public: void func(){} }; int main() { shared_ptr< Hoge > p( new Hoge() ); return 0; }
ここでshared_ptrをshared_ptrにコピーすると、sp_counted_impl_pのインスタンスが共有され、sharedの値のみインクリメントされます。
shared_ptr< Hoge > p( new Hoge() );
shared_ptr< Hoge > q( p );
同様にshared_ptrをweak_ptrにコピーすると、sp_counted_impl_pのインスタンスが共有され、weakの値のみインクリメントされます。
shared_ptr< Hoge > p( new Hoge() );
weak_ptr< Hoge > q( p );
以下のようにshared_ptrのデストラクタが先に呼ばれてsharedがデクリメント、インスタンスがdeleteされると、weakもデクリメントされます。この時weak==0になればsp_counted_impl_pもdeleteされます。今回の例ではweak==1なので、sp_counted_impl_pはまだdeleteされません。
weak_ptr< Hoge > q;
{
shared_ptr< Hoge > p( new Hoge() );
q = p;
}←ここ
weak_ptrが消滅すると、weakのみデクリメントされます。weak==0であれば、sp_counted_impl_pもdeleteされます。
{
weak_ptr< Hoge > q;
{
shared_ptr< Hoge > p( new Hoge() );
q = p;
}
}←ここ
weak_ptrが指すインスタンスが既にdeleteされている可能性がある場合、lockすることで安全にアクセスできます。lock時にshared==0であれば、nullptrを指すshared_ptrが返されます。
{ weak_ptr< Hoge > q; { shared_ptr< Hoge > p( new Hoge() ); q = p; } if( shared_ptr< Hoge > temp = q.lock() ) { temp->func();//呼ばれない } }
- インスタンスを渡してshared_ptrを生成するとshared=1、weak=1
- shared_ptrをコピーするとsharedのみインクリメント
- shared_ptrが破棄される場合、sharedのみデクリメント
- この時shared==0になれば、インスタンスをdelete
- さらにweakもデクリメントし、weak==0になれば、sp_counted_impl_pもdelete
- この時shared==0になれば、インスタンスをdelete
- weak_ptrをコピーするとweakのみインクリメント
- weak_ptrが破棄される場合、weakのみデクリメント
- この時weak==0になれば、sp_counted_impl_pもdelete
- この場合sharedは既に0になっており、インスタンスもdelete済み
- この時weak==0になれば、sp_counted_impl_pもdelete
以上のことを踏まえて、次回weak_ptrを実装します。
TinyXML + babelを使ってみる。(1)
TinyXML
babel
C++で作られた各種文字コード( シフトJIS, JIS, EUC, UNICODE(UTF8, UTF16, UTF32) )変換ライブラリです。
以上のライブラリを使って、XMLの実験をします。
最終的にはWPFのXAMLのように、アプリケーションの外観の設定やボタンの配置など出来たらいいなと。
テスト用XML
- test.xml
<?xml version="1.0" ?> <app> <window title="title" w="720" h="480" > <button x="10" y="10" w="100" h="30">ボタン</button> </window> <window title="たいとる" w="320" h="240" /> </app>
まずはチュートリアル通りdumpしてみる
一番下にあるソースをコピペします。
#include "tinyxml.h" // ---------------------------------------------------------------------- // STDOUT dump and indenting utility functions // ---------------------------------------------------------------------- const unsigned int NUM_INDENTS_PER_SPACE=2; const char * getIndent( unsigned int numIndents ) { static const char * pINDENT=" + "; static const unsigned int LENGTH=strlen( pINDENT ); unsigned int n=numIndents*NUM_INDENTS_PER_SPACE; if ( n > LENGTH ) n = LENGTH; return &pINDENT[ LENGTH-n ]; } // same as getIndent but no "+" at the end const char * getIndentAlt( unsigned int numIndents ) { static const char * pINDENT=" "; static const unsigned int LENGTH=strlen( pINDENT ); unsigned int n=numIndents*NUM_INDENTS_PER_SPACE; if ( n > LENGTH ) n = LENGTH; return &pINDENT[ LENGTH-n ]; } int dump_attribs_to_stdout(TiXmlElement* pElement, unsigned int indent) { if ( !pElement ) return 0; TiXmlAttribute* pAttrib=pElement->FirstAttribute(); int i=0; int ival; double dval; const char* pIndent=getIndent(indent); printf("\n"); while (pAttrib) { printf( "%s%s: value=[%s]", pIndent, pAttrib->Name(), pAttrib->Value()); if (pAttrib->QueryIntValue(&ival)==TIXML_SUCCESS) printf( " int=%d", ival); if (pAttrib->QueryDoubleValue(&dval)==TIXML_SUCCESS) printf( " d=%1.1f", dval); printf( "\n" ); i++; pAttrib=pAttrib->Next(); } return i; } void dump_to_stdout( TiXmlNode* pParent, unsigned int indent = 0 ) { if ( !pParent ) return; TiXmlNode* pChild; TiXmlText* pText; int t = pParent->Type(); printf( "%s", getIndent(indent)); int num; switch ( t ) { case TiXmlNode::DOCUMENT: printf( "Document" ); break; case TiXmlNode::ELEMENT: printf( "Element [%s]", pParent->Value() ); num=dump_attribs_to_stdout(pParent->ToElement(), indent+1); switch(num) { case 0: printf( " (No attributes)"); break; case 1: printf( "%s1 attribute", getIndentAlt(indent)); break; default: printf( "%s%d attributes", getIndentAlt(indent), num); break; } break; case TiXmlNode::COMMENT: printf( "Comment: [%s]", pParent->Value()); break; case TiXmlNode::UNKNOWN: printf( "Unknown" ); break; case TiXmlNode::TEXT: pText = pParent->ToText(); printf( "Text: [%s]", pText->Value() ); break; case TiXmlNode::DECLARATION: printf( "Declaration" ); break; default: break; } printf( "\n" ); for ( pChild = pParent->FirstChild(); pChild != 0; pChild = pChild->NextSibling()) { dump_to_stdout( pChild, indent+1 ); } } // load the named file and dump its structure to STDOUT void dump_to_stdout(const char* pFilename) { TiXmlDocument doc(pFilename); bool loadOkay = doc.LoadFile(); if (loadOkay) { printf("\n%s:\n", pFilename); dump_to_stdout( &doc ); // defined later in the tutorial } else { printf("Failed to load file \"%s\"\n", pFilename); } } int main() { dump_to_stdout( "test.xml" ); return 0; }
実行結果
日本語部分は文字化けしてますが、それ以外は当然問題無いですね。
test.xml: Document + Declaration + Element [app] (No attributes) + Element [window] + title: value=[title] + w: value=[720] int=720 d=720.0 + h: value=[480] int=480 d=480.0 3 attributes + Element [button] + x: value=[10] int=10 d=10.0 + y: value=[10] int=10 d=10.0 + w: value=[100] int=100 d=100.0 + h: value=[30] int=30 d=30.0 4 attributes + Text: [繝懊ち繝ウ] + Element [window] + title: value=[縺溘>縺ィ繧犠 + w: value=[320] int=320 d=320.0 + h: value=[240] int=240 d=240.0 3 attributes
UTF-8→シフトJIS変換
TinyXMLは文字コードの変換に対応していないので、今回はbabelを使って変換してみます。init_babel()で初期化してから、utf8_to_sjis()を使って変換します。
#include "babel.h" int main() { babel::init_babel(); ..略.. return 0; }
- テキストノードの取得
std::string str = babel::utf8_to_sjis( pText->Value() );
- 属性の取得
std::string str = babel::utf8_to_sjis( pAttrib->Value() );
実行結果
正しく変換できました。
test.xml: Document + Declaration + Element [app] (No attributes) + Element [window] + title: value=[title] + w: value=[720] int=720 d=720.0 + h: value=[480] int=480 d=480.0 3 attributes + Element [button] + x: value=[10] int=10 d=10.0 + y: value=[10] int=10 d=10.0 + w: value=[100] int=100 d=100.0 + h: value=[30] int=30 d=30.0 4 attributes + Text: [ボタン] + Element [window] + title: value=[たいとる] + w: value=[320] int=320 d=320.0 + h: value=[240] int=240 d=240.0 3 attributes