プライベートなメンバ関数の存在意義って?

アバター
へろりくしょん
記事: 92
登録日時: 14年前
住所: 福岡

プライベートなメンバ関数の存在意義って?

投稿記事 by へろりくしょん » 14年前

最近なんかまた C++ 熱が上がってぽちぽちといじっているのです。

それで、思うのですが。 プライベートメンバ変数。 外部クラスから触れないのはいいのですが、クラスの中側からはさわり放題です。
クラスという閉じられた世界の中だけですが、あたかもグローバル変数であるかのような錯覚に陥ります。 クラスが肥大化すると大変危険だと思うのですが、どうなんでしょう。

そしてメンバ関数です。 関数というのは原則、1機能を閉じこめるべきで、これは完全に関数内で完結されているべきです。 それはプライベートなメンバ関数といえど変わりません。 まぁ、私見ですが。

ですが、メンバ関数から、同クラスのメンバ変数はさわり放題です。 おさわり自由です。 なんというサービス精神。
私はこれが気持ち悪くてたまりません。

ところで、私ですが、1クラス1ファイルの精神をいたく気に入っております。 これ、C++のお作法的にはどうなんですか? 悪いという話は聞きませんが。

そうするとですね。 プライベートなメンバ関数は翻訳単位に閉じこめる事が出来るのですよ。 メンバ関数として定義せずに、翻訳単位に静的に定義してしまうんです。
メンバ関数ではありませんから、当然基本引数として与えられない限り、クラスのメンバ変数にアクセスする事は出来ません。
おさわり自由の気持ち悪さから解放されます。 堅牢性もアップ。 びばおさわり不自由。

最近の私のお気に入りです。

でも、こうなると、プライベートなメンバ関数の存在意義ってなんですか?
シングルトンクラス等でコンストラクタをプライベートにするといった特殊な用途以外に使い道ってありますか?

そういうものなんでしょうか。 お作法的にはどうなんでしょう。 よく分かりません。 誰か教えてください。

ISLe
記事: 2650
登録日時: 14年前

Re: プライベートなメンバ関数の存在意義って?

投稿記事 by ISLe » 14年前

#肥大化は問題外として…。

フレームワークライブラリを作ったりすると公開されたメンバ関数のワークフロー以外から勝手に呼ばれると困るメンバ関数あたりをプライベートにしますよ。
ゲームだとハードウェアのネイティブな部分にアクセスするコードとかあちこちで呼び出す同じ処理を関数にまとめてます。

メンバ関数にしないとプライベートなメンバ変数にはコンパイルエラーでアクセスできなくないですか?
フレンド関数?

メンバ関数の本体は基本的にヘッダファイルには書かないです。これも勝手に変えられては困るから。
メンバ関数本体がある程度大きければ翻訳単位を分けることもします。
ヘッダファイルをコンパクトにしてコンパイル済みのオブジェクトファイルやライブラリ形式で使えばコンパイル時間を短縮できます。

継承先からアクセスできるかできないかというprotectedとの違いも考えると存在意義が見えてくるのではないでしょうか。

アバター
Justy
副管理人
記事: 122
登録日時: 14年前

Re: プライベートなメンバ関数の存在意義って?

投稿記事 by Justy » 14年前

へろり さんが書きました:クラスが肥大化すると大変危険だと思うのですが、どうなんでしょう。
 そうですね。肥大化すれば、グローバル変数と大して変わらなくなったりもします。

へろり さんが書きました:1クラス1ファイル
 特に問題ないと思います。

へろり さんが書きました:プライベートなメンバ関数は翻訳単位に閉じこめる事
 1クラス1ファイルでないと成り立たない話ではありますが、判らなくもないです。
 私もそのクラスでしか使わない、且つメンバへのアクセスが最小限なケースではそうすることもあります。

 しかし、へろりさんのケースの場合、使い勝手とかメンテナンス性が悪くなりませんか?
 普通の関数の中からですと同じクラスの(publicにしろ privateにしろ)他のメンバ関数が呼びだし辛いですし、
メンバ変数を引数で渡すということは引数の数や内容が変わると呼び出し元も含めて全部変更しないと
いけなくなるかと思うのですが。

へろり さんが書きました:プライベートなメンバ関数の存在意義
 本来の存在意義が気持ち悪いとなると、結構限られてきますね。

 例えば
・ templateでの内部処理の記述
・ 外部から直接呼び出されたくない仮想関数

とか。

アバター
へろりくしょん
記事: 92
登録日時: 14年前
住所: 福岡

Re: プライベートなメンバ関数の存在意義って?

投稿記事 by へろりくしょん » 14年前

ISLe さんが書きました:メンバ関数にしないとプライベートなメンバ変数にはコンパイルエラーでアクセスできなくないですか?
フレンド関数?
エラーが出ていいんです。 アクセスされるのが気持ち悪いのですから。 そのメンバ関数の機能上全く関係の無いメンバ変数へさえもフルアクセス出来るというのはいささか危険な香りがします。

ISLe さんが書きました:メンバ関数の本体は基本的にヘッダファイルには書かないです。これも勝手に変えられては困るから。
メンバ関数本体がある程度大きければ翻訳単位を分けることもします。
私もメンバ関数の実体をヘッダファイルに書くような事はしません。 これはどちらかと言うと勝手に変えられては困るからというよりも、そのクラスを利用する側はその実装を知る必要が無いからです。
同じ理由で、クラスを利用する側は、そのクラスがどのようなプライベートなメンバ関数を持っているか等、知る必要はありません。 どうせ利用出来ないんですから。
基本的に、ヘッダファイルへはそのヘッダファイルを利用する側が必要とする情報のみを書くべきだと思ってます。

ちなみに、個人的には、関数の数や規模で翻訳単位を分けるのはいささかどうかと思います。 翻訳単位はあくまでも機能別に分けるべきだと思うのですが。
私が、1クラス1ファイルを好むのは、このあたりが主な理由です。

今回の趣旨は、次のようにするべきではないか。 ということです。

#hoge.h

CODE:

typedef struct HOGE_DAT_ST*		HOGE_DAT;

class CHoge{
public:
	CHoge(void);
private:
	HOGE_DAT data;
};
#hoge.cpp

CODE:

#include "hoge.h"

struct HOGE_DAT_ST{
	...
};

static void foo(HOGE_DAT data);
static HOGE_DAT baa(void);

CHoge::CHoge(void)
{
	...
}

void foo(HOGE_DAT data)
{
	...
}

HOGE_DAT baa(void)
{
	...
}
本来ならば、関数 foo() baa() はプライベートなメンバ関数として持つべきところでしょうが、上記のように翻訳単位に閉じこめてしまったらどうかという事です。
構造体 HOGE_DAT_ST は、ウィンドウハンドルのような使われ方をすると想定した場合、クラスの利用側からも見える必要はありますが、その内部構成を知る必要はありませんので、ヘッダファイルでは不完全型としています。
ですが、クラス内部では HOGE_DAT_ST にアクセス出来なければ、お話になりませんね。

ISLe さんが書きました:継承先からアクセスできるかできないかというprotectedとの違いも考えると存在意義が見えてくるのではないでしょうか。
継承先からアクセスできるのは非常に大きなメリットだとは思いますが、どのみち継承元のプライベートなメンバ関数にはアクセス出来ませんので、やっぱり継承先と言えど、継承元のプライベートなメンバ関数が何かを知る必要は無いと思います。

アバター
へろりくしょん
記事: 92
登録日時: 14年前
住所: 福岡

Re: プライベートなメンバ関数の存在意義って?

投稿記事 by へろりくしょん » 14年前

ちょっと長くなったので分けました。

Justy さんが書きました: しかし、へろりさんのケースの場合、使い勝手とかメンテナンス性が悪くなりませんか?
 普通の関数の中からですと同じクラスの(publicにしろ privateにしろ)他のメンバ関数が呼びだし辛いですし
言われてみればそうですね。
メンテナンス性はともかく、使い勝手は非常に悪そうです。
他のメンバ関数が呼び出しづらいというよりも、呼び出す事が邪悪な気さえします。
関数呼び出しツリーの末端に近い部分の関数でしか出来そうにありませんね。
まぁ、私の経験が不足しているためか、クラス内で完結したコールツリーはあまり深くなったことはありませんが。
Justy さんが書きました:メンバ変数を引数で渡すということは引数の数や内容が変わると呼び出し元も含めて全部変更しないと
いけなくなるかと思うのですが。
このあたりは、pure C では基本的に常にそうなので、あまり気にした事はありませんが、より簡便に使えるチャンスをとりのがしているのかな、と考えるとなんだか損した気になりますね。

ISLe
記事: 2650
登録日時: 14年前

Re: プライベートなメンバ関数の存在意義って?

投稿記事 by ISLe » 14年前

プライベート変数とそれをアクセスする部分を隠すということだと勘違いしてました。

ポインタや参照のメンバ変数だけを公開してクラスの実装を翻訳単位に隠すことはわたしもします。
インターフェースだけを定義したクラスを公開して実際には継承したクラスのインスタンスをメンバに持たせたりもします。
先に書いたネイティブな部分にアクセスする部分とかクラスにまとめてポインタをたらい回ししたりしてます。
HOGE_DAT_STにアクセスする必要がある(中身を知っている)翻訳単位でHOGE_DAT_STの定義をインクルードすれば複数の翻訳単位で必要最小限にもできます。

HOGE_DAT_STがクラスではダメな理由は何でしょう?
fooやbarがHOGE_DAT_STのメンバ関数でいけない理由は無いような気がします。

まったく関係の無いプライベートメンバが共存しているクラスは設計がおかしいと思います。

一翻訳単位のコンパイルにまいど5分とかかかるようになるとさすがに分けたくなるのでその辺は勘弁して下さい。
逆にほとんど中身のないインターフェースクラスを実装クラスと同じように機能別に分けるとスカスカになってしまいますし。
最後に編集したユーザー ISLe on 2011年2月26日(土) 17:53 [ 編集 2 回目 ]

アバター
へろりくしょん
記事: 92
登録日時: 14年前
住所: 福岡

Re: プライベートなメンバ関数の存在意義って?

投稿記事 by へろりくしょん » 14年前

ISLe さんが書きました:HOGE_DAT_STがクラスではダメな理由は何でしょう?
fooやbarがHOGE_DAT_STのメンバ関数でいけない理由は無いような気がします。
別にダメというわけではありません。 問題の趣旨をわかりやすく説明する為に一例として上げてみただけですので。

ただ、それ自体が何かの機能を有するわけでも無く、データの塊としての構造体だった場合、初期化関数だったり、セッターやゲッターのみをメンバ関数に持つクラスにする理由もちょっとわかりません。
構造体はとりあえずクラス化を検討してみるというのが、C++ 的お作法なのでしょうか。 java なんかは言語仕様レベルでこれを強要してきますね。

また、私は1クラス1ファイルハッピーなので、そのクラス内部でしか使われないのに、別の翻訳単位に細かく切り分けるのもちょっとどうかなと思ったりする訳です。
やっぱり、1クラス1ファイルというのはあまりよろしくないのでしょうか。 こういうケースにのみ例外を認めるべきでしょうか。 一般的にはどうするのがお作法なんでしょう。
ISLe さんが書きました:まったく関係の無いプライベートメンバが共存しているクラスは設計がおかしいと思います。
上記の例では、プライベートメンバ関数は存在していないはずですが、これはどういう意味でしょうか。
また、ファイル .cpp で定義されている関数は、そのクラスでしか使われないので関係無い事は無いと思うのですが。

クラスの実体が書かれている翻訳単位に静的な非メンバな関数を定義してはいけないという意味でしょうか。

ISLe
記事: 2650
登録日時: 14年前

Re: プライベートなメンバ関数の存在意義って?

投稿記事 by ISLe » 14年前

へろり さんが書きました:ただ、それ自体が何かの機能を有するわけでも無く、データの塊としての構造体だった場合、初期化関数だったり、セッターやゲッターのみをメンバ関数に持つクラスにする理由もちょっとわかりません。
構造体はとりあえずクラス化を検討してみるというのが、C++ 的お作法なのでしょうか。 java なんかは言語仕様レベルでこれを強要してきますね。
翻訳単位でまとめることができるならば初期化関数やゲッター,セッターは必要ないのでは?

#hoge.h

CODE:

typedef struct HOGE_DAT_ST*		HOGE_DAT;

class CHoge{
public:
	CHoge(void);
private:
	HOGE_DAT data;
};
#hoge.cpp

CODE:

#include "hoge.h"

struct HOGE_DAT_ST{
	...
	void foo();
	HOGE_DAT_ST();
};

CHoge::CHoge(void)
{
	...
}

void HOGE_DAT_ST::foo()
{
	...
}

HOGE_DAT_ST::HOGE_DAT_ST()
{
	...
}
これだけだと違いは見えませんけどここから先の拡張性を考えると記述が楽です。
(追記)ネームスペースごとのアウトライン表示できる統合開発環境ではfoo関数の関連性が分かり易くなります。
へろり さんが書きました:
ISLe さんが書きました:まったく関係の無いプライベートメンバが共存しているクラスは設計がおかしいと思います。
上記の例では、プライベートメンバ関数は存在していないはずですが、これはどういう意味でしょうか。
また、ファイル .cpp で定義されている関数は、そのクラスでしか使われないので関係無い事は無いと思うのですが。
メンバ関数の機能上全く関係の無いメンバ変数へさえもフルアクセス出来るのが気持ち悪いと書かれていたのでむしろカプセル化するべきと思ったのです。
脈絡無くてすみません。
これもCHogeの実装からHOGE_DAT_STのすべてのメンバに自由にアクセスできるのが気持ち悪いという話だと勘違いしていました。
自分が何を勘違いしているかも分かってなかったです。すみません。

#このコメントも見当違いなことを書いている気がします。
最後に編集したユーザー ISLe on 2011年2月27日(日) 18:17 [ 編集 1 回目 ]