パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
ファイル入出力クラスの追加(1)

KTxtEditプロジェクトのファイル入出力クラスが利用する文字コードリストとストリームバッファを作成する。

ファイル入出力クラス(TMyFileOpenSave)は複数文字コードに対応する他に、最近使ったファイルの履歴機能を実装する。

KTxtEditは共有インクルードファイル(MyInterfaceType.h)のTMyFileOption構造体でこれらをまとめて扱う。履歴機能はこれを記憶し、履歴に残るファイルを読み込むと文字コード/BOM/改行コードを自動設定する。

構造体 メンバ 説明
TMyFileOption encoding_ wxString iconv定義文字コードを表現する文字列
byteOrderMark_ bool BOMの有無(非ユニコードは必ずfalse)
endOfLine_ EOL列挙型 改行コード(CRLF、LF、CR)

本項目はTMyFileOpenSaveが利用する二つのクラスを作成し、その一つは文字コードリストクラス(TMyEncodings)で、もう一つはwxTextCtrlデータに直接アクセスするヒープストリームバッファ(TMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBuf)である。TMyFileOpenSaveは次ステップで作成する。

プログラミング手順

  1. codecvtファセットクラステンプレートのコピー
  2. 文字コードリストクラス(TMyEncodings)の作成
  3. オプション設定ダイアログ(TMyOptionDialog)の修正
  4. ヒープストリームバッファ(TMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBuf)の作成
  5. リポジトリ更新とビルドテスト

codecvtファセットクラステンプレートのコピー

自作のcodecvtファセットインクルードファイル(TMyCodeCvt.h)をKTxtEditプロジェクトディレクトリにコピーあるいは作成してプロジェクト全ターゲットに追加する。使用するのはTMyCodeCvtクラステンプレート特殊化で、内部型をウィンドウズコントロールがネイティブサポートする16ビット符号長(符号をwchar_tに格納する)のUTF-16、外部型をファイル保存に指定する文字コードとする。外部型は8ビット符号長(符号をcharに格納する)に固定されるため、UTF-16/UTF-32などの16/32ビット符号長文字コードはエンディアンを明示してバイト列として扱う。

文字コードリストクラス(TMyEncodings)の作成

KTxtEditの扱う文字コードのリストを定義するクラスとしてTMyEncodingsを作成する。TMyEncodings.h/TMyEncodings.cppにTMyEncodingsクラススケルトンを作成して修正する。このクラスはシングルトンpimplイディオムで実装する。扱う文字コードはTMyEncodings.hpp内でFILE_ENCODING_TRAITSマクロでクラス定義する。

マクロ 仮引数 説明
FILE_ENCODING_TRAITS NAME クラス名、C++命名規則に従い例えばハイフン(-)は使えない
ENCODING iconvの理解する文字コード名、ダブルコーテーション(")で囲む
CANHAVEBOM 文字コードのBOM所有可否、ユニコードはtrue、非ユニコードはfalse

文字コードを定義したら必ずTMyEncodingList型リストに追加する。TMyEncodingListはboostメタプログラミングライブラリのboost::mpl::list特殊化で、TMyEncodingsはTMyEncodingListから選択可能な文字コードリストを取得する。iconvの扱う文字コードは全て任意に定義、追加できる。

覚え書き
このようにKTxtEditの扱う文字コードはソースコードに書き込まれコンパイル時に決定する。実はiconvライブラリにそういった制約はなく、iconv_open関数に文字コード指定文字列を与えるだけで実行時に容易に変更できる。KTxtEditでコンパイル時決定とした理由はTMyCodeCvtクラステンプレートを書いた際の怠慢の結果であるが、実行時に扱う文字コードの変更を許すのは何となく危険を感じてそのままとした。

SetItemContainerControlメンバ関数はwxChoiceなどのwxItemContainer派生コントロールに文字コード選択リストを作成し、各項目のクライアントデータ(void*型)にBOM所有可否のブール値をキャストして収納する。

説明
CodeCvt std::codecvt<wchar_t,char,mbstate_t>
メンバ関数 機能 返値 説明 仮引数 説明
GetInstance シングルトン取得するスタティックメンバ関数 const TMyEncodings& TMyEncodingsインスタンス - -
SetItemContainerControl コントロールに文字コード選択リストを作成 void - wxItemContainer* リスト作成するコントロール
GetCodeCvt codecvtファセットの取得 CodeCvt* codecvtファセット const char* 文字コード

TMyEncodings.h

#ifndef TMYENCODINGS_H
#define TMYENCODINGS_H
class TMyEncodings final
{
public:
using CodeCvt=std::codecvt<wchar_t,char,mbstate_t>;
private:
MY_PIMPL(std::unique_ptr,Impl,pimpl_);
private:
TMyEncodings();
public:
static const TMyEncodings& GetInstance();
TMyEncodings(const TMyEncodings&)=delete;
TMyEncodings& operator=(const TMyEncodings&)=delete;
~TMyEncodings();
void SetItemContainerControl(wxItemContainer* ctrl) const;
CodeCvt* GetCodeCvt(const char* name) const;
};
#endif // TMYENCODINGS_H

TMyEncodings.cpp

#include "wx_pch.h"
#include "TMyEncodings.h"
#include "TMyCodeCvt.h"
#include <map>
#include <algorithm>
#include <functional>
#include <boost/mpl/list.hpp>
#include <boost/mpl/for_each.hpp>
namespace
{
#define FILE_ENCODING_TRAITS(NAME,ENCODING,CANHAVEBOM) struct NAME \
{ \
using type=char; \
static const char* encoding() {return ENCODING;} \
static bool canhavebom() {return CANHAVEBOM;} \
};
FILE_ENCODING_TRAITS(UTF8,"UTF-8",true);
FILE_ENCODING_TRAITS(UTF16LE,"UTF-16LE",true);
FILE_ENCODING_TRAITS(UTF16BE,"UTF-16BE",true);
FILE_ENCODING_TRAITS(UTF32LE,"UTF-32LE",true);
FILE_ENCODING_TRAITS(UTF32BE,"UTF-32BE",true);
FILE_ENCODING_TRAITS(CP932,"CP932",false);
FILE_ENCODING_TRAITS(SHIFTJIS,"SHIFT-JIS",false);
FILE_ENCODING_TRAITS(EUCJP,"EUC-JP",false);
FILE_ENCODING_TRAITS(JIS,"ISO-2022-JP",false);
using TMyEncodingList=boost::mpl::list
<UTF8
,UTF16LE
,UTF16BE
,UTF32LE
,UTF32BE
,CP932
,SHIFTJIS
,EUCJP
,JIS>::type;
}
class TMyEncodings::Impl
{
private:
struct StrCmp
{
bool operator()(const char* lhs,const char* rhs) const {return std::strcmp(lhs,rhs)<0;}
};
using CodeCvtMap=std::map<const char*,std::pair<std::function<CodeCvt*()>,bool>,StrCmp>;
const CodeCvtMap map_;
private:
struct CreateCodeCvtMapFunctor
{
CodeCvtMap& map_;
CreateCodeCvtMapFunctor(CodeCvtMap& map):map_{map} {}
template<typename T> void operator()(T)
{
map_.insert(std::make_pair(T::encoding(),std::make_pair
([](){return new TMyCodeCvt<TMyCodeCvtStateIconv<NMyEncoding::UTF16,T>>;}
,T::canhavebom())));
}
};
static CodeCvtMap CreateCodeCvtMap()
{
CodeCvtMap map;
boost::mpl::for_each<TMyEncodingList>(CreateCodeCvtMapFunctor{map});
return map;
}
public:
Impl():map_{CreateCodeCvtMap()} {};
void SetItemContainerControl(wxItemContainer* ctrl) const
{
ctrl->Clear();
for (const auto& r:map_)
{ctrl->Append(r.first,reinterpret_cast<void*>(r.second.second));}
}
CodeCvt* GetCodeCvt(const char* name) const {return map_.at(name).first();}
};
TMyEncodings::TMyEncodings():pimpl_{new Impl{}} {}
TMyEncodings::~TMyEncodings() {}
const TMyEncodings& TMyEncodings::GetInstance()
{
static const TMyEncodings onlyInstance{};
return onlyInstance;
}
void TMyEncodings::SetItemContainerControl(wxItemContainer* ctrl) const {pimpl_->SetItemContainerControl(ctrl);}
TMyEncodings::CodeCvt* TMyEncodings::GetCodeCvt(const char* name) const {return pimpl_->GetCodeCvt(name);}

オプション設定ダイアログ(TMyOptionDialog)の修正

TMyEncodingsクラスによる文字コード選択リストは[File|Open]および[File|Save]のファイル選択ダイアログとオプション設定ダイアログ(TMyOptionDialog)それぞれのwxChoiceコントロールが利用する。先にOptionDialogダイアログ[File]ページのChoiceDefaultEncodingコントロールを修正する。TMyOptionDialog.cppに"TMyEncodings.h"のインクルードを追加する。TMyOptionDialog::ImplクラスのExecuteメンバ関数冒頭でChoiceDefaultEncodingにダミーとして"UTF-8"のみリスト追加していた行をTMyEncodingsのSetItemContainerControlメンバ関数で置き換える。もしPanelFileのEnabledプロパティのチェックを外して[File]ページを無効化しているのなら、この機会にチェックに戻す。

TMyOptionDialog.cpp

#include "wx_pch.h"
#include "TMyOptionDialog.h"
#include "OptionDialog.h"
#include "TMyRegKey.h"
#include "version_macro.h"
#include "TMyEncodings.h"
#include <set>
...
class TMyOptionDialog::Impl
{
private:
...
public:
...
bool Execute(wxWindow* parent)
{
OptionDialog optionDlg{parent};
TMyEncodings::GetInstance().SetItemContainerControl(optionDlg.ChoiceDefaultEncoding);
optionDlg.ChoiceDefaultEndOfLine->Append(_("CRLF"),reinterpret_cast<void*>(EOL::CRLF));
optionDlg.ChoiceDefaultEndOfLine->Append(_("LF"),reinterpret_cast<void*>(EOL::LF));
optionDlg.ChoiceDefaultEndOfLine->Append(_("CR"),reinterpret_cast<void*>(EOL::CR));
...
}
...
};
...

ヒープストリームバッファ(TMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBuf)の作成

KTxtEditはテキスト編集コントロールとしてwxTextCtrlを用いる。ウィンドウズ実装はwxTextCtrlをオペレーティングシステム供給のエディットコントロールで実現し、エディットコントロールはデータをヒープメモリに保持する。文字コード変換を伴うファイル入出力の高速化を図るためヒープメモリに直接アクセスするストリームバッファを作成する。このヒープメモリは16ビットウィンドウ時代にローカルヒープと呼ばれたメモリを管理するAPI関数(LocalXXX)で扱うが、32ビット以降はローカル/グローバルヒープの区別を失い互換性維持にのみ意味が残る。

TMyLocalAllocStreamBuf.h/TMyLocalAllocStreamBuf.cppにTMyLocalAllocStreamBufクラススケルトンを作成するが、TMyLocalAllocStreamBufは用いずスケルトンは全て削除する。ストリームバッファは出力/入力にそれぞれTMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBufクラスを作成し、つまり一つのクラスで入出力を兼用しない。この選択は単なる怠惰で、C++標準ライブラリ供給のストリームバッファは入出力兼用なのでどちらもstd::wstreambuf(すなわちstd::basic_streambuf<wchar_t>)を継承する。これらはファイルストリームバッファstd::wfilebuf(すなわちstd::basic_buf<wchar_t>)と直接データ交換してファイル出力/入力する。

TMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBufは改行コードを処理する。改行コードはデータとしてはLFだがファイル出力でCRLF、LF、CRに変換する三つの流儀があり、通常ならファイルストリームバッファ側が処理する。ウィンドウズならwfilebufをopenメンバ関数の第二実引数オープンモードにbinaryを加えて(バイナリモード)開けばLF(無変換)、加えずに(テキストモード)開けばCRLF(変換)となる。しかしこれでは以下の不具合を持つため、TMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBuf側がこれらを処理してファイルストリームバッファ側はバイナリモードで開く。

  • ヒープメモリから直接読み出すデータの改行コードはLFでなくマイクロソフト標準のCRLFである。
  • ウィンドウズ実装basic_filebufは改行コードをCRへ変換できない。
  • バイト単位の処理なので16/32ビット符号長の改行コードを正しく処理できない。

TMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBufはpimplイディオムを用いて、ファクトリーメソッド(または仮想コンストラクタ)で各ファイル改行コードによるロジック実装クラステンプレートの特殊化(ImplBase派生クラス)に処理を実装する。両者のインターフェースはコンストラクタを除きwstreambufを継承する。TMyLocalAllocIStreamBufは読み出すヒープメモリをコンストラクタに与えるが、TMyLocalAllocOStreamBufは自らヒープメモリを作成して書き込み後にReplaceパブリックメンバ関数で外部のヒープメモリ(例えばエディットコントロールが所有するヒープメモリ)に置換する。ヒープメモリのデータ終端はヌル(0)を前提とする。

メンバ関数 機能 返値 説明 仮引数 説明
TMyLocalAllocOStreamBuf コンストラクタ - - TMyFileOption::EOL 改行コード
Replace ヒープメモリ置換 HLOCAL 書き込まれたヒープメモリ HLOCAL 置換するヒープメモリ
(wstreambuf継承関数) ... ... ... ... ...
メンバ関数 機能 返値 説明 仮引数 説明
TMyLocalAllocIStreamBuf コンストラクタ - - HLOCAL 読み出すヒープメモリ
TMyFileOption::EOL 改行コード
(wstreambuf継承関数) ... ... ... ... ...

TMyLocalAllocIStreamBuf.h

#ifndef TMYLOCALALLOCSTREAMBUF_H
#define TMYLOCALALLOCSTREAMBUF_H
#include "MyInterfaceType.h"
class TMyLocalAllocOStreamBuf:public std::wstreambuf
{
private:
using EOL=TMyFileOption::EOL;
private:
MY_PIMPL(std::unique_ptr,ImplBase,pimpl_);
template<EOL> class Impl;
protected:
int_type overflow(int_type c) override;
int sync() override;
public:
explicit TMyLocalAllocOStreamBuf(EOL eol);
TMyLocalAllocOStreamBuf(const TMyLocalAllocOStreamBuf& rhs)=delete;
TMyLocalAllocOStreamBuf& operator=(const TMyLocalAllocOStreamBuf& rhs)=delete;
TMyLocalAllocOStreamBuf(TMyLocalAllocOStreamBuf&& rhs);
TMyLocalAllocOStreamBuf& operator=(TMyLocalAllocOStreamBuf&& rhs);
~TMyLocalAllocOStreamBuf() override;
HLOCAL Replace(HLOCAL hndEx);
};
class TMyLocalAllocIStreamBuf:public std::wstreambuf
{
private:
using EOL=TMyFileOption::EOL;
private:
MY_PIMPL(std::unique_ptr,ImplBase,pimpl_);
template<EOL> class Impl;
protected:
int_type underflow() override;
public:
explicit TMyLocalAllocIStreamBuf(HLOCAL hnd,EOL eol);
TMyLocalAllocIStreamBuf(const TMyLocalAllocIStreamBuf& rhs)=delete;
TMyLocalAllocIStreamBuf& operator=(const TMyLocalAllocIStreamBuf& rhs)=delete;
TMyLocalAllocIStreamBuf(TMyLocalAllocIStreamBuf&& rhs);
TMyLocalAllocIStreamBuf& operator=(TMyLocalAllocIStreamBuf&& rhs);
~TMyLocalAllocIStreamBuf() override;
};
#endif // TMYLOCALALLOCSTREAMBUF_H

TMyLocalAllocIStreamBuf.cpp

#include "wx_pch.h"
#include "TMyLocalAllocStreamBuf.h"
#include <algorithm>
namespace
{
template<TMyFileOption::EOL> constexpr wchar_t GetEol();
template<> constexpr wchar_t GetEol<TMyFileOption::EOL::LF>() {return L'\n';}
template<> constexpr wchar_t GetEol<TMyFileOption::EOL::CR>() {return L'\r';}
}
namespace
{
class TMyHLOCAL final
{
private:
HLOCAL hnd_;
const bool own_;
mutable void* ptr_;
private:
void InvalidatePtr()
{
if (ptr_) {::LocalUnlock(hnd_);}
ptr_=nullptr;
};
public:
explicit TMyHLOCAL(SIZE_T uBytes):TMyHLOCAL{::LocalAlloc(LHND,uBytes),true} {}// Allocation fail check might be required.
explicit TMyHLOCAL(HLOCAL hnd,bool own=false):hnd_{hnd},own_{own},ptr_{nullptr} {}
TMyHLOCAL(const TMyHLOCAL&)=delete;
TMyHLOCAL& operator=(const TMyHLOCAL&)=delete;
TMyHLOCAL(TMyHLOCAL&&)=default;
TMyHLOCAL& operator=(TMyHLOCAL&&)=default;
~TMyHLOCAL()
{
InvalidatePtr();
if (own_) {::LocalFree(hnd_);}
}
SIZE_T GetSize() const {return ::LocalSize(hnd_);}
void* GetPtr() const
{
if (!ptr_) {ptr_=::LocalLock(hnd_);}
return ptr_;
}
void ReAllocate(SIZE_T uBytes)
{
InvalidatePtr();
if (own_)
{
if (auto hndNew=::LocalReAlloc(hnd_,uBytes,0)) {hnd_=hndNew;}// Allocation fail check might be required.
}
}
HLOCAL Replace(HLOCAL hnd)
{
InvalidatePtr();
std::swap(hnd,hnd_);
return hnd;
}
};
}
class TMyLocalAllocOStreamBuf::ImplBase
{
public:
virtual int_type overflow(int_type c)=0;
virtual int sync()=0;
public:
virtual ~ImplBase() {};
static ImplBase* Create(TMyLocalAllocOStreamBuf* tHis,EOL eol);
virtual HLOCAL Replace(HLOCAL hndEx)=0;
};
template<TMyFileOption::EOL N> class TMyLocalAllocOStreamBuf::Impl:public ImplBase
{
private:
static constexpr int bufSize_=100;
static constexpr int blockBytes_=1024;
TMyLocalAllocOStreamBuf* const this_;
wchar_t buf_[bufSize_];
TMyHLOCAL mem_;
wchar_t* head_;
wchar_t* pos_;
private:
int GetNumToWrite(wchar_t* pbase,wchar_t* pptr)
{
return pptr-pbase+std::count(pbase,pptr,GetEol<N>());
}
wchar_t* WriteOnMem(wchar_t* pbase,wchar_t* pptr,wchar_t* pos)
{
for (const auto* p=pbase;p!=pptr;++p,++pos)
{
if (*p!=GetEol<N>()) {*pos=*p;}
else {*pos=L'\r';*(++pos)=L'\n';}
}
return pos;
}
public:
int_type overflow(int_type c) override
{
sync();
if (c!=EOF) {*this_->pptr()=c;this_->pbump(1);}
return c;
}
int sync() override
{
auto writtenNum=pos_-head_;
auto requiredBytes=(writtenNum+GetNumToWrite(this_->pbase(),this_->pptr()))*sizeof(wchar_t);
if (requiredBytes>mem_.GetSize())
{
mem_.ReAllocate(((requiredBytes-1)/blockBytes_+1)*blockBytes_);
head_=static_cast<wchar_t*>(mem_.GetPtr());
pos_=head_+writtenNum;
}
pos_=WriteOnMem(this_->pbase(),this_->pptr(),pos_);
this_->pbump(this_->pbase()-this_->pptr());
return 0;
}
public:
Impl(TMyLocalAllocOStreamBuf* tHis):this_{tHis}
,buf_{0}
,mem_{blockBytes_}
,head_{static_cast<wchar_t*>(mem_.GetPtr())}
,pos_{head_}
{
this_->setp(buf_,buf_+bufSize_);
}
HLOCAL Replace(HLOCAL hndEx) override
{
*this_->pptr()=0;this_->pbump(1);
sync();
//mem_.ReAllocate((pos_-head_)*sizeof(wchar_t)); // No need to shrink size because the edit control manages it.
return mem_.Replace(hndEx);
}
};
template<> int TMyLocalAllocOStreamBuf::Impl<TMyFileOption::EOL::CRLF>::GetNumToWrite(wchar_t* pbase,wchar_t* pptr)
{
return pptr-pbase;
}
template<> wchar_t* TMyLocalAllocOStreamBuf::Impl<TMyFileOption::EOL::CRLF>::WriteOnMem(wchar_t* pbase,wchar_t* pptr,wchar_t* pos)
{
return std::copy(pbase,pptr,pos);
}
TMyLocalAllocOStreamBuf::ImplBase* TMyLocalAllocOStreamBuf::ImplBase::Create(TMyLocalAllocOStreamBuf* tHis,EOL eol)
{
switch (eol)
{
case EOL::CRLF:
return new Impl<EOL::CRLF>{tHis};
case EOL::LF:
return new Impl<EOL::LF>{tHis};
case EOL::CR:
return new Impl<EOL::CR>{tHis};
default:
assert(false);return nullptr; // Never reach here.
}
}
TMyLocalAllocOStreamBuf::TMyLocalAllocOStreamBuf(EOL eol):pimpl_{ImplBase::Create(this,eol)} {}
TMyLocalAllocOStreamBuf::TMyLocalAllocOStreamBuf(TMyLocalAllocOStreamBuf&& rhs):pimpl_{std::move(rhs.pimpl_)} {}
TMyLocalAllocOStreamBuf& TMyLocalAllocOStreamBuf::operator=(TMyLocalAllocOStreamBuf&& rhs) {pimpl_=std::move(rhs.pimpl_);return *this;}
TMyLocalAllocOStreamBuf::~TMyLocalAllocOStreamBuf() {}
TMyLocalAllocOStreamBuf::int_type TMyLocalAllocOStreamBuf::overflow(int_type c) {return pimpl_->overflow(c);}
int TMyLocalAllocOStreamBuf::sync() {return pimpl_->sync();}
HLOCAL TMyLocalAllocOStreamBuf::Replace(HLOCAL hndEx) {return pimpl_->Replace(hndEx);}
class TMyLocalAllocIStreamBuf::ImplBase
{
public:
virtual int_type underflow()=0;
public:
virtual ~ImplBase() {};
static ImplBase* Create(TMyLocalAllocIStreamBuf* tHis,HLOCAL hnd,EOL eol);
};
template<TMyFileOption::EOL N> class TMyLocalAllocIStreamBuf::Impl:public ImplBase
{
private:
static constexpr int rangeSize_=100;
static constexpr int putbackSize_=4;
static constexpr int bufSize_=rangeSize_+putbackSize_;
TMyLocalAllocIStreamBuf* const this_;
wchar_t buf_[bufSize_];
const TMyHLOCAL mem_;
const wchar_t* pos_;
public:
int_type underflow() override
{
if (*pos_==0) {return EOF;}
if (this_->gptr()>=this_->egptr())
{
auto numPutback=this_->gptr()-this_->eback();
if (numPutback>putbackSize_) {numPutback=putbackSize_;} // std::min makes putbackSize_ ODR-used in Debug config.
auto* tgt=std::copy(this_->gptr()-numPutback,this_->gptr(),buf_);
for (auto posEnd=pos_+rangeSize_;pos_<posEnd&&*pos_;++pos_,++tgt)
{
switch (*pos_)
{
case L'\r':
if (*(pos_+1)==L'\n') {++pos_;}// Intentionally no break;
case L'\n':
*tgt=GetEol<N>();break;
default:
*tgt=*pos_;break;
}
}
this_->setg(buf_,buf_+numPutback,tgt);
}
return *this_->gptr();
}
public:
Impl(TMyLocalAllocIStreamBuf* tHis,HLOCAL hnd):this_{tHis}
,buf_{0}
,mem_{hnd}
,pos_{static_cast<wchar_t*>(mem_.GetPtr())}
{
this_->setg(buf_,buf_,buf_);
}
};
template<> class TMyLocalAllocIStreamBuf::Impl<TMyFileOption::EOL::CRLF>:public ImplBase
{
private:
const TMyHLOCAL mem_;
public:
int_type underflow() override {return EOF;}
public:
Impl(TMyLocalAllocIStreamBuf* tHis,HLOCAL hnd):mem_{hnd}
{
auto* buf=static_cast<wchar_t*>(mem_.GetPtr());
tHis->setg(buf,buf,std::find(buf,buf+mem_.GetSize()/sizeof(wchar_t),0));
}
};
TMyLocalAllocIStreamBuf::ImplBase* TMyLocalAllocIStreamBuf::ImplBase::Create(TMyLocalAllocIStreamBuf* tHis,HLOCAL hnd,EOL eol)
{
switch (eol)
{
case EOL::CRLF:
return new Impl<EOL::CRLF>{tHis,hnd};
case EOL::LF:
return new Impl<EOL::LF>{tHis,hnd};
case EOL::CR:
return new Impl<EOL::CR>{tHis,hnd};
default:
assert(false);return nullptr; // Never reach here.
}
}
TMyLocalAllocIStreamBuf::TMyLocalAllocIStreamBuf(HLOCAL hnd,EOL eol):pimpl_{ImplBase::Create(this,hnd,eol)} {}
TMyLocalAllocIStreamBuf::TMyLocalAllocIStreamBuf(TMyLocalAllocIStreamBuf&& rhs):pimpl_{std::move(rhs.pimpl_)} {}
TMyLocalAllocIStreamBuf& TMyLocalAllocIStreamBuf::operator=(TMyLocalAllocIStreamBuf&& rhs) {pimpl_=std::move(rhs.pimpl_);return *this;}
TMyLocalAllocIStreamBuf::~TMyLocalAllocIStreamBuf() {}
TMyLocalAllocIStreamBuf::int_type TMyLocalAllocIStreamBuf::underflow() {return pimpl_->underflow();}

リポジトリ更新とビルドテスト

ビルドにはiconvライブラリファイルの追加を必要とする。[Project|Build options]で開く[Project build options]ダイアログでダミーターゲット(_DeployStartと_DeployEnd)を除く全ターゲットの[Linker setting]ページ[Link libraris]にlibiconv.aを追加する。動作確認(4)を参考にGit for Windowsリポジトリを更新する。必要ならビルド実行するが、クライアント(TMyCoreImpl)側を書き換えていないため変更は実行に反映しない。

UMLクラス図

次ステップで作成するTMyFileOpenSaveクラスのヘルパーを作成しただけなのでUMLクラス図によるアプリケーション構成の説明は省略し、代わりにTMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBufについて説明する。

  • TMyLocalAllocOStreamBuf/TMyLocalAllocIStreamBufはC++標準ライブラリwstreambufを継承し、前者はoverflowとsync、後者はunderflowをオーバーライドする。どちらもクラス内定義のImplBase派生クラスを実装ロジックとするpimplイディオムで実装する。
  • ImplBaseは抽象クラスでファクトリーメソッド(仮想コンストラクタ)のCreateスタティックメンバ関数で指定されたファイル改行コードを処理する派生クラスを構築する。
  • ImplBase派生クラスはTMyFileOption::EOL列挙型をテンプレート仮引数とするImplクラステンプレートとする。
  • TMyLocalAllocOStreamBuf::ImplクラステンプレートのEOL::CRLFによる特殊化は改行コード無変換かつ2符号長で他と処理が異なり、これを理由にGetNumWrite、WriteOnMemメンバ関数は明示的特殊化とする。無変換ではあるが他と共通のヒープ領域管理も必要なのでクラス全体は明示的特殊化としない。
  • TMyLocalAllocIStreamBuf::ImplクラステンプレートのEOL::CRLFによる特殊化は改行コード無変換なのでクラス全体を明示的特殊化として処理を大幅に簡略化する。
  • TMyHLOCALヘルパークラスはヒープを管理するウィンドウズAPI関数(LocalXXX)をまとめる。