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 + 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を使ってみる。(1)

boost::asioを使いたかったのですが、ネットワークの知識が乏しいので、まずはwinsockの勉強をしようと思います。

参考サイト

とりあえず以下のように使えるSocketクラス、SocketStreamクラスを作ってみます。
開発環境はwindows7VC++2008EEです。XP、Vistaでも問題無いはず。

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 );
        };
    }
}
  • ClientSocketImpl
    • add_ref
    • release
    • get_stream
    • connect
    • create
      • ClientSocketを生成して返す
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;
        }
    }
}

使ってみる

サンプル
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

  • shared_ptr02.zip 直
    • VC++2008EEで動作確認
    • boostとの違い
      • namespaceはhogeとしています
      • sp_counted_baseをshared_object_baseに変更
      • sp_counted_impl_pをshared_object_implに変更
      • sp_counted_impl_pdをshared_object_impl_delに変更
#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
  • weak_ptrをコピーするとweakのみインクリメント
  • weak_ptrが破棄される場合、weakのみデクリメント
    • この時weak==0になれば、sp_counted_impl_pもdelete

以上のことを踏まえて、次回weak_ptrを実装します。

TinyXML + babelを使ってみる。(1)

TinyXML

C++で作られたシンプルで軽量なXMLパーサです。速度は速くは無いですが、扱いやすいライブラリです。

babel

C++で作られた各種文字コードシフトJIS, JIS, EUC, UNICODE(UTF8, UTF16, UTF32) )変換ライブラリです。

以上のライブラリを使って、XMLの実験をします。
最終的にはWPFXAMLのように、アプリケーションの外観の設定やボタンの配置など出来たらいいなと。

テスト用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