質問するログイン新規登録
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

初心者

初心者は、プログラミングやITに不慣れな方が、基礎的な知識やスキルを身につける際に直面する疑問や課題に関する投稿に使用されます。入門書や学習サイトで学び始めた方、初めての開発環境構築でつまずいた方などに向けた質問が多く見られます。

意見交換

8回答

385閲覧

インタフェースクラスと Pimpl の違い(?)

fana

総合スコア12253

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

初心者

初心者は、プログラミングやITに不慣れな方が、基礎的な知識やスキルを身につける際に直面する疑問や課題に関する投稿に使用されます。入門書や学習サイトで学び始めた方、初めての開発環境構築でつまずいた方などに向けた質問が多く見られます。

0グッド

1クリップ

投稿2025/08/08 01:50

編集2025/08/08 01:57

0

1

C++初心者ですが, "Pimpl" っていうのが気になっています.

"Pimpl" というのは,例えば↓みたいなやつだと, XXX を使う箇所に "YYY.h", "ZZZ.h" への依存関係が生じてしまい,それが嫌だから……

C++

1//[XXX.h] 2#include "YYY.h" 3#include "ZZZ.h" 4 5class XXX 6{ 7public: 8 void PublicMethod1(); 9 void PublicMethod2(); 10private; //こいつらのせいで依存関係が生まれる 11 YYY m_YYY; 12 ZZZ m_ZZZ; 13};

それを避けるために

C++

1//[XXX.h] Pimpl版 2class XXX 3{ /* ctor とか dtor のあたりは省略 */ 4public: 5 void PublicMethod1(); 6 void PublicMethod2(); 7private; //具体的な実装は Impl の中に書くことにして…… 8 class Impl; 9 std::unique_ptr< Impl > m_pImpl; 10};

とかしておいて,各 public メソッドは Impl に処理を移譲する:

C++

1//[XXX.cpp] 2#include "XXX.h" 3#include "YYY.h" 4#include "ZZZ.h" 5 6//隠蔽された実装 7class XXX::Impl 8{ 9public: 10 void PublicMethod1(){ /*略*/ } 11 void PublicMethod2(){ /*略*/ } 12private; 13 YYY m_YYY; 14 ZZZ m_ZZZ; 15}; 16 17//処理を移譲 18void XXX::PublicMethod1(){ m_pImpl->PublicMethod1(); } 19void XXX::PublicMethod2(){ m_pImpl->PublicMethod2(); }

……っていう話だと思うんですが,
これって要は「 XXX はpublicなインタフェースだけを決めていて,その具体実装は Impl 」ってことですよね.

であれば,以下のような形でもよい,というか素直な形(?)ではなかろうかと思うのですが,どうなんでしょう.

C++

1//[IXXX.h] 2 3//インタフェースを決めたいのなら 4//こうやってインタフェースを決めて…… 5class IXXX 6{ 7public: 8 virtual void PublicMethod1() = 0; 9 virtual void PublicMethod2() = 0; 10}; 11 12//あとは実装を得る手段を用意すればそれで済むのではなかろうか? 13std::unique_ptr< IXXX > CreateXXX();

C++

1//[XXX.cpp] 2#include "IXXX.h" 3#include "YYY.h" 4#include "ZZZ.h" 5 6namespace 7{//隠蔽された実装 8 class XXX : public IXXX 9 { 10 public: 11 virtual void PublicMethod1() override { /*略*/ } 12 virtual void PublicMethod2() override { /*略*/ } 13 private: 14 YYY m_YYY; 15 ZZZ m_ZZZ; 16 }; 17} 18 19std::unique_ptr< IXXX > CreateXXX(){ return std::make_unique<XXX>(); }

両者(他の形もあるかもですが)の{違い,使い分け/選択基準,etc...}というのはどんな感じなのでしょうか?
( Pimpl の側を{使うべき,使わなきゃならない,使ったほうが良い, etc... }場面とは?)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

回答8

#1

fana

総合スコア12253

投稿2025/08/08 02:45

ひょっとしたら Pimpl というのは,
XXX を実装する際に最初から Pimpl なる形で実装しようぜ」っていうような話ではなくて ,
「既に最初の形(:YYYやZZZへの依存をばら撒く形)の XXX が存在している(使われている)状況」というのが出発点にあって,その状態からやれること……っていう話なのだろうか?

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#2

SaitoAtsushi

総合スコア5741

投稿2025/08/08 12:24

どちらでも用に足りるでしょうし、これといった欠点もないと思います。

実際に COM (Component Object Model) のインターフェイスは抽象クラスを用いた構成でバイナリ互換性が実現されていて Windows ではありふれたものです。 Windows アプリケーションを作るなら避けて通れない基礎技術として定着しています。

ただ、一般的な意識として抽象クラスは動的多態のためのものなので「XXXXXX::Impl のインターフェイスである」という一対一の関係がある (多態性がない) ときに使うのは不自然に思われます。 実装が変わる可能性があるという意味では広い意味では多態なのかもしれませんが……。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#3

lehshell

総合スコア1181

投稿2025/08/08 15:15

個人的には。。。
クラスを他に公開する場合は Pimpl を使って private メンバーを隠したい。
インタフェースクラスは
このインタフェースクラスの参照に、インタフェースを継承したクラスのインスタンスを渡して処理を行いたい状況
このインタフェースクラスのポインタに、インタフェースを継承したクラスのインスタンスのアドレスを渡して処理を行いたい状況
が想定できる場合に使いたい
ですね。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#4

fana

総合スコア12253

投稿2025/08/09 08:46

ふーむ,お二方とも

C++

1std::unique_ptr< IXXX > CreateXXX( /*何か引数でもあって*/ ) 2{ /*引数に応じて返される具体的な型は違うよ!*/ }

みたいな話(「ファクトリ」?)であれば違和感はないけども,具体実装が1種類固定っていう場合にはなんか違う感……ってことですかね.

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#5

lehshell

総合スコア1181

投稿2025/08/09 15:10

編集2025/08/09 16:20

インターフェースについては C# や Java のように該当インターフェースを持つクラスを定義する時に使用して Pimpl については例えば Input クラスを他に公開する場合

C++

1// Input.hpp 2重include 防止や一部のコンストラクタ抑制は省略している 2#include <map> 3#include <utility> // pair 4 5// Mouse 部分省略 6class Input final { 7public: 8 Input() = default; // Constructor 9 ~Input() = default; // Destructor 10 bool GetKey(int keyCode) const; // true: KeyDown 状態 11 bool GetKeyDown(int keyCode) const; // true: KeyUp → KeyDown 検出あり 12 bool GetKeyUp(int keyCode) const; // true: KeyDown → KeyUp 検出あり 13private: 14 mutable std::map<int, bool> keyDown; // KeyUp -> KeyDown 検出情報保存用 15 mutable std::map<int, bool> keyUp; // KeyDown -> KeyUp 検出情報保存用 16};

とするより Pimpl を使ってメンバー関数の実装だけでなく private なメンバー変数 keyDown keyUp まで隠したい

C++

1// Input.hpp 2#include <utility> // pair 3#include <memory> // unique_ptr 4 5class Input final { 6public: 7 Input(); // Constructor 8 ~Input(); // Destructor 9 bool GetKey(int keyCode) const; // true: KeyDown 状態 10 bool GetKeyDown(int keyCode) const; // true: KeyUp → KeyDown 検出あり 11 bool GetKeyUp(int keyCode) const; // true: KeyDown → KeyUp 検出あり 12private: 13 class Impl; 14 std::unique_ptr<Impl> pImpl; 15};

C++

1// Input.cpp 2#include <map> 3#include "Input.hpp" 4 5class Input::Impl { 6public: 7 Impl() = default; // Constructor 8 ~Impl() = default; // Destructor 9 bool GetKeyDown(int keyCode) const; // true: KeyUp → KeyDown 検出あり 10 bool GetKeyUp(int keyCode) const; // true: KeyDown → KeyUp 検出あり 11private: 12 mutable std::map<int, bool> keyDown; // KeyUp -> KeyDown 検出情報保存用 13 mutable std::map<int, bool> keyUp; // KeyDown -> KeyUp 検出情報保存用 14}; 15 16bool Input::Impl::GetKeyDown(int keyCode) const { 省略 } 17bool Input::Impl::GetKeyUp(int keyCode) const { 省略 } 18 19Input::Input() { pImpl = std::make_unique<Impl>(); } // Constructor 20Input::~Input() {} // Destructor 21bool Input::GetKeyDown(int keyCode) const { 22 return pImpl->GetKeyDown(keyCode); 23} 24bool Input::GetKeyUp(int keyCode) const { 25 return pImpl->GetKeyUp(keyCode); 26}

というのは
mutable std::map<int, bool> keyDown; // KeyUp -> KeyDown 検出情報保存用
mutable std::map<int, bool> keyUp; // KeyDown -> KeyUp 検出情報保存用

mutable std::map<int, bool> prevKey; // 前回の KEY 状態
と実装を変更しても Input.hpp の再リリースをしなくて済むためです。

これを

C++

1class IInput { 2public: 3 virtual bool GetKey(int keyCode) const = 0; 4 virtual bool GetKeyDown(int keyCode) const = 0; 5 virtual bool GetKeyUp(int keyCode) const = 0; 6}; 7 8class Input : public IInput { 9 // 省略 10}; 11 12std::unique_ptr<IInput> CreateInput(); 13 14// CreateInput() 利用ソース 15std::unique_ptr<IInput>pIInput = CreateInput();

とすると Interface のポインタを使うことになりますが、これであれば IInput を使わずに
単に

C++

1class Input { 2 // 省略 3}; 4std::unique_ptr<Input> CreateInput(); 5 6// CreateInput() 利用ソース 7std::unique_ptr<Input>pInput = CreateInput();

とする方が素直に感じます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#6

matukeso

総合スコア1686

投稿2025/08/10 00:20

同じどっちを使ってもいいとはおもいます。
pimplだと継承できるのと(pimplしているクラスなら普通は継承しないけど)、コンストラクタ毎のCreate関数ラッパを書かなくていいのと、(LTCGとか使えば)インライン展開が狙える程度の利点はあります。
昔のVisualStudioは、interfaceの呼び出し側(pinterface→func)から実装に飛ぶ事が出来なかったりしましたが(virtual func() = 0に飛ぶ)、いまは実装クラスの関数を探して飛んでくれますし。
個人的には、pimpl目的ならInterface内にstaticなCreate関数を用意してpimpl目的を強調する手を使っていました。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#7

fana

総合スコア12253

投稿2025/08/11 04:39

編集2025/08/11 04:42

#5 の最後のところ:

これであれば IInput を使わずに…(コード)…とする方が素直に感じます。

のコード内にて「省略」とされている class Input の中身というのはどんな感じにするという話なのでしょう?


#6

確かに ctor の種類が多い場合にその分だけ CreateXXX が並んでいるというのはちょっと嫌な風景かも.

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#8

lehshell

総合スコア1181

投稿2025/08/11 15:52

#7 「省略」とされている class Input の中身というのはどんな感じにする

すみません、Pimpl のつもりでした。

C++

1class Input { 2public: 3 // 省略 4private: 5 class Impl; 6 std::unique_ptr<Impl> pImpl; 7}; 8std::unique_ptr<Input> CreateInput(); 9 10// CreateInput() 利用ソース 11std::unique_ptr<Input>pInput = CreateInput();

が、こうしてしまうと Input インスタンスのすべての public メンバー関数が使えてしまうため

C++

1... 2std::unique_ptr<IInput> CreateInput(); 3 4// CreateInput() 利用ソース 5std::unique_ptr<IInput>pIInput = CreateInput();

として IInput のインターフェースの関数に制約させることができないため同じにはなりませんね。
思慮が足りていませんでした。m(__)m

#5 は取り下げます。失礼しました。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

この意見交換はまだ受付中です。

会員登録して回答してみよう

アカウントをお持ちの方は

関連した質問