パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
pimplイディオムの導入(1)

KTxtEditプロジェクトにpimplイディオムを導入しクラスインターフェースとロジック実装を分離する。

前ステップでエディタとしての基本機能の実装は完了したが、さらなる機能拡張を容易とするリファクタリングとしてクラスをpimpl化する。

プログラミング手順

  1. プリコンパイル対象インクルードの修正
  2. アプリケーションクラスのpimpl化
  3. メインウィンドウクラスのpimpl化
  4. リポジトリ更新とビルドテスト

プリコンパイル対象インクルードの修正

本サイトのpimplポインタは必ずC++標準ライブラリのスマートポインタunique_ptrあるいはshared_ptrに保持するため、プリコンパイル対象インクルードに<memory>を追加して全ソースコードファイルにインクルードさせる。ロジック実装クラスは全てImplネストクラス(クラススコープで定義されるクラス)とし、pimplポインタはMY_PIMPLマクロで定義する。

wx_pch.h

#ifndef WX_PCH_H_INCLUDED
#define WX_PCH_H_INCLUDED
...
#ifdef WX_PRECOMP
// put here all your rarely-changing header files
//... std
#include <memory>
//... boost
//... wxWidgets
#endif // WX_PRECOMP
#define MY_PIMPL(smart_ptr,cls,name) class cls;smart_ptr<cls> name;
#endif // WX_PCH_H_INCLUDED

アプリケーションクラスのpimpl化

通常pimplポインタはコンストラクタ/デストラクタで構築/解体するが、wxAppから派生するアプリケーションクラスはOnInit/OnExitメンバ関数で初期化/終了処理を行うことが推奨されるので、pimplポインタもこれに従う。KTxtEditAppのlocale_メンバ変数をロジック実装(Impl)クラスへ移し、OnInitメンバ関数内処理の一部をImplクラスコンストラクタ内へ移す。

KTxtEditApp.h

...
class KTxtEditApp : public wxApp
{
private:
MY_PIMPL(std::unique_ptr,Impl,pimpl_);
public:
bool OnInit() override;
int OnExit() override;
};
...

KTxtEditApp.cpp

...
class KTxtEditApp::Impl
{
private:
wxLocale locale_;
public:
Impl():locale_{}
{
wxStandardPaths::Get().IgnoreAppSubDir("Debug*");
wxStandardPaths::Get().IgnoreAppSubDir("Release*");
locale_.Init(wxLANGUAGE_DEFAULT,wxLOCALE_DONT_LOAD_DEFAULT);
locale_.AddCatalog(MYAPPINFO_NAME);
locale_.AddCatalog("wxstd");
}
};
bool KTxtEditApp::OnInit()
{
auto temp=std::unique_ptr<Impl>{new Impl{}};
//(*AppInitialize
bool wxsOK = true;
wxInitAllImageHandlers();
if ( wxsOK )
{
KTxtEditFrame* Frame = new KTxtEditFrame(0);
Frame->Show();
SetTopWindow(Frame);
}
//*)
if (wxsOK) {std::swap(pimpl_,temp);}
return wxsOK;
}
int KTxtEditApp::OnExit()
{
auto temp=std::unique_ptr<Impl>{};
std::swap(pimpl_,temp);
return wxApp::OnExit();
}
...

メインウィンドウクラスのpimpl化

KTxtEditFrameクラスメンバ変数helpController_とfileFullPath_をロジック実装(Impl)クラスへ移し、メインウィンドウおよび子ウィンドウのポインタform_、textCtrl_、statusBar_をコピー保持させ、KTxtEditFrameコンストラクタ内処理の一部をImplクラスコンストラクタ内へ移す。helpController_がImplクラスへ移ったため<wx/help.h>インクルードもKTxtEditFrame.hからKTxtEditFrame.cppへ移す。ImplクラスにKTxtEditFrameイベントハンドラと同名のメンバ関数を加えてその処理を移し、イベントハンドラはこれをコールする。ファイル変更確認をConfirmIfModifiedプライベートメンバ関数で共通化した以外の変更は加えていない。

KTxtEditFrame.h

//(*Headers(KTxtEditFrame)
#include <wx/frame.h>
//*)
class KTxtEditFrame: public wxFrame
{
public:
...
private:
...
//(*Declarations(KTxtEditFrame)
wxStatusBar* statusBar_;
wxTextCtrl* textCtrl_;
//*)
MY_PIMPL(std::unique_ptr,Impl,pimpl_);
DECLARE_EVENT_TABLE()
};

KTxtEditFrame.cpp

KTxtEditFrame::ImplクラスをKTxtEditFrameコンストラクタおよびメンバ関数より前に置く。

#include "wx_pch.h"
#include <wx/msgdlg.h>
#include <wx/stdpaths.h>
#include <wx/filename.h>
#include <wx/help.h>
#include "KTxtEditFrame.h"
#include "iconimages/bitmapHelp.xpm"
#include "iconimages/bitmapAbout.xpm"
#include "iconimages/bitmapQuit.xpm"
#include "version_macro.h"
...
class KTxtEditFrame::Impl
{
private:
KTxtEditFrame* const form_;
wxTextCtrl* const textCtrl_;
wxStatusBar* const statusBar_;
wxHelpController helpController_;
wxString fileFullPath_;
private:
bool ConfirmIfModified() const
{
return !textCtrl_->IsModified()
||wxMessageBox(_("The current file is modified. Do you want to discard?")
,MYAPPINFO_NAME,wxICON_QUESTION|wxYES_NO|wxNO_DEFAULT|wxCENTER)==wxYES;
}
public:
Impl(KTxtEditFrame* form):form_{form}
,textCtrl_{form_->textCtrl_},statusBar_{form_->statusBar_}
,helpController_{},fileFullPath_{}
{
auto iconBundle=wxIconBundle{};
iconBundle.AddIcon(wxIcon{"aaaa",wxICON_DEFAULT_TYPE,16,16});
iconBundle.AddIcon(wxIcon{"aaaa",wxICON_DEFAULT_TYPE,32,32});
form_->SetIcons(iconBundle);
helpController_.Initialize
(wxFileName{wxStandardPaths::Get().GetDataDir(),MYAPPINFO_NAME ".chm"}
.GetFullPath());
}
void OnNew(wxCommandEvent& event)
{
if (ConfirmIfModified())
{
textCtrl_->Clear();
}
}
void OnOpen(wxCommandEvent& event)
{
if (ConfirmIfModified())
{
wxFileDialog fileDlg{form_,_("Open a file")
,wxEmptyString,wxEmptyString,wxEmptyString,wxFD_OPEN|wxFD_FILE_MUST_EXIST};
if (fileDlg.ShowModal()==wxID_OK)
{
if (textCtrl_->LoadFile(fileDlg.GetPath()))
{
fileFullPath_=fileDlg.GetPath();
}
else
{
wxMessageBox(_("Failed to open a file."),MYAPPINFO_NAME,wxICON_ERROR|wxCENTER);
}
}
}
}
void OnSave(wxCommandEvent& event)
{
if (fileFullPath_.IsEmpty())
{
OnSaveAs(event);
}
else
{
if (!textCtrl_->SaveFile(fileFullPath_))
{
wxMessageBox(_("Failed to save the current file."),MYAPPINFO_NAME,wxICON_ERROR|wxCENTER);
}
}
}
void OnSaveAs(wxCommandEvent& event)
{
wxFileDialog fileDlg{form_,_("Save the current file")
,wxEmptyString,fileFullPath_,wxEmptyString,wxFD_SAVE|wxFD_OVERWRITE_PROMPT};
if (fileDlg.ShowModal()==wxID_OK)
{
if (textCtrl_->SaveFile(fileDlg.GetFilename()))
{
fileFullPath_=fileDlg.GetFilename();
}
else
{
wxMessageBox(_("Failed to save the current file."),MYAPPINFO_NAME,wxICON_ERROR|wxCENTER);
}
}
}
void OnQuit(wxCommandEvent& event)
{
form_->Close();
}
void OnUndo(wxCommandEvent& event)
{
textCtrl_->Undo();
}
void OnRedo(wxCommandEvent& event)
{
textCtrl_->Redo();
}
void OnCut(wxCommandEvent& event)
{
textCtrl_->Cut();
}
void OnCopy(wxCommandEvent& event)
{
textCtrl_->Copy();
}
void OnPaste(wxCommandEvent& event)
{
textCtrl_->Paste();
}
void OnHelp(wxCommandEvent& event)
{
helpController_.DisplayContents();
}
void OnAbout(wxCommandEvent& event)
{
wxString msg;
msg<<MYAPPINFO_NAME<<"\n"
<<_("Version: ")<<AUTOVERSION_STR_VER_BUILD_COUNT_STATUS<<"\n"
<<_("Date: ")<<AUTOVERSION_STR_DATE_DMY_MONTH_SHORT_NAME;
wxMessageBox(msg, _("Welcome to..."));
}
void OnIdle(wxIdleEvent& event)
{
form_->SetTitle(wxString::Format("%s%s - " MYAPPINFO_NAME
,textCtrl_->IsModified()?"*":""
,fileFullPath_.IsEmpty()?_("(Unnamed)"):fileFullPath_));
auto x=long{};auto y=long{};
textCtrl_->PositionToXY(textCtrl_->GetInsertionPoint(),&x,&y);
statusBar_->SetStatusText(wxString::Format("%d:%d",y+1,x+1),1);
form_->GetMenuBar()->FindItem(idMenuSave)->Enable(textCtrl_->IsModified());
form_->GetMenuBar()->FindItem(idMenuUndo)->Enable(textCtrl_->CanUndo());
form_->GetMenuBar()->FindItem(idMenuRedo)->Enable(textCtrl_->CanRedo());
form_->GetMenuBar()->FindItem(idMenuCut)->Enable(textCtrl_->CanCut());
form_->GetMenuBar()->FindItem(idMenuCopy)->Enable(textCtrl_->CanCopy());
form_->GetMenuBar()->FindItem(idMenuPaste)->Enable(textCtrl_->CanPaste());
event.Skip();
}
void OnClose(wxCloseEvent& event)
{
if (event.CanVeto()&&!ConfirmIfModified())
{
event.Veto();
}
else
{
event.Skip();
}
}
};
KTxtEditFrame::KTxtEditFrame(wxWindow* parent,wxWindowID id):pimpl_{nullptr}
{
...

Implクラスインスタンスのpimpl_メンバ変数は初期化リストではヌルとし、wxSmithが挿入する//(*Initialize(KTxtEditFrame) ... //*)の後でpimplポインタを構築しunique_ptr::reset関数に与える。KTxtEditFrameイベントハンドラ実装をpimpl_の対応メンバ関数コールに修正する。

KTxtEditFrame::KTxtEditFrame(wxWindow* parent,wxWindowID id):pimpl_{nullptr}
{
//(*Initialize(KTxtEditFrame)
...
//*)
Connect(wxEVT_IDLE,(wxObjectEventFunction)&KTxtEditFrame::OnIdle);
pimpl_.reset(new Impl{this});
}
KTxtEditFrame::~KTxtEditFrame()
{
//(*Destroy(KTxtEditFrame)
//*)
}
void KTxtEditFrame::OnQuit(wxCommandEvent& event)
{
pimpl_->OnQuit(event);
}
void KTxtEditFrame::OnAbout(wxCommandEvent& event)
{
pimpl_->OnAbout(event);
}
void KTxtEditFrame::OnHelp(wxCommandEvent& event)
{
pimpl_->OnHelp(event);
}
void KTxtEditFrame::OnNew(wxCommandEvent& event)
{
pimpl_->OnNew(event);
}
void KTxtEditFrame::OnOpen(wxCommandEvent& event)
{
pimpl_->OnOpen(event);
}
void KTxtEditFrame::OnSave(wxCommandEvent& event)
{
pimpl_->OnSave(event);
}
void KTxtEditFrame::OnSaveAs(wxCommandEvent& event)
{
pimpl_->OnSaveAs(event);
}
void KTxtEditFrame::OnUndo(wxCommandEvent& event)
{
pimpl_->OnUndo(event);
}
void KTxtEditFrame::OnRedo(wxCommandEvent& event)
{
pimpl_->OnRedo(event);
}
void KTxtEditFrame::OnCut(wxCommandEvent& event)
{
pimpl_->OnCut(event);
}
void KTxtEditFrame::OnCopy(wxCommandEvent& event)
{
pimpl_->OnCopy(event);
}
void KTxtEditFrame::OnPaste(wxCommandEvent& event)
{
pimpl_->OnPaste(event);
}
void KTxtEditFrame::OnIdle(wxIdleEvent& event)
{
pimpl_->OnIdle(event);
}
void KTxtEditFrame::OnClose(wxCloseEvent& event)
{
pimpl_->OnClose(event);
}

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

動作確認(4)を参考にGit for Windowsリポジトリを更新する。必要ならビルド実行する。

UMLクラス図

簡略したUMLクラス図でアプリケーションの構成を説明する。

  • KTxtEditAppクラスとKTxtEditFrameクラスのそれぞれにロジック実装クラス(KTxtEditApp::Impl、KTxtEditFrame::Impl)を加える。