• ベストアンサー
※ ChatGPTを利用し、要約された質問です(原文:ポインタのキャストについて)

ポインタのキャストについて

このQ&Aのポイント
  • ポインタのキャストについて説明します。HOGEクラスとHOGE_Derivedクラスについてのコード例を使用して、メモリ上のデータの並びについて説明します。
  • また、HOGE_Derivedクラスで演算子のオーバーロードを行っているコードについても説明します。
  • memcpyを使用したコピーの方法について処理系の依存性や自然さについても考察しています。

質問者が選んだベストアンサー

  • ベストアンサー
回答No.31

C++(Cの範囲まで含めると)は相当に複雑で また自由度が高い分プログラマの理解と注意力を要求する言語です。 従って 言語仕様とプログラミングの手法、設計全て込みになってくると、もはや 「知らない、分からない事がある方が遥かに普通」と思った方が良いですね。 考えようによってはプログラミングも芸術の一種です。 基本的なことほど、「調べないので逆に案外気づかない」とかいう事もあります。 世の中のC++のプラグラマー全体の内、今回の質問・回答で出てきたこと全ての内容について、なにも参照し直さず すらすら答えられる人など、1%もいないんじゃないかという気がします。 なので、もし今後分からない事が出てきたりしたら、どんどん聞いたり調べたりしてみてください。 そうした方が後で自分が助かります。 まぁ、今回の内容に関してはおそらくほとんど網羅できたんではないかと思いますし、よほどの事がない限り私はここいらで「回答サイド」は一時失礼する、かもしれませんw 何しろ、そろそろ自分のプログラムの方に集中すべき時間になってきたっぽいのでw 数年がかりの物ですが、まさにそろそろクライマックスか!?って感じです。 そんではノシ

katorea21
質問者

お礼

ご自身の作業時間を削ってまでお付き合い頂き、大変ありがとうございました。 改めて基本的な部分から見直すきっかけとなり、大変有意義なQ&Aとなりました。 ベストアンサーは決めにくいので、とりあえず本回答をベストアンサーとして、質問を締め切らせて頂きます。 ご回答頂いた全ての皆様、ありがとうございました。

その他の回答 (30)

回答No.30

>コンパイル時にクラス型のサイズも確定していて、それが固定値として実行モジュールに書き込まれてしまうと言うことですね? そう言う事です。 なので同じコードだとしても、別の環境で全く同じコードのままコンパイルしたのを使うと、死ぬ可能性があります。 そこはしっかり確認しておく必要があります。

katorea21
質問者

お礼

これに関しては、初歩的な事なのにちゃんと理解してなくて恥ずかしいです。 まあ実際それほど特殊な処理系に持って行くことはあまり想定していないため、普通にINTELの互換CPU上でさえ動作すれば十分かなと。CPUのメカニズムとデータ型のサイズの関係など、そのあたりは正直詳しくないので、必要になったら調べたいと思います。

回答No.29

そんで、テンプレートではなく 一般的に言う「インターフェース」クラス的なことであれば 既に class Base はそんな感じになっていますね。(あとはやりたい一般的な処理の概念の数に応じて仮想関数増やせばいい、という感じで)

回答No.28

>ここまで来ると、Baseはテンプレートクラスにして「A」と言う型を意識しないような作りにしておく方が良いような気もします。(テンプレートというかインタフェースクラス?) 「再コンパイルの手間」ってことを考えると、テンプレートは結構かかりかねないですよ?w 仮に下のコードの構造をBaseクラスの実装を「ヘッダに書かず」「ソースに書く」としとけば 最強にラッキーな仕様変更や実装方法(GetAとかやらずにgetterを個別に書いたうえで)の場合 再コンパイルすべきソースは「たったの一つ」で済む可能性すらあります。 まぁ、GetA()->メンバとかやってたとしても そのソースとBaseクラスのソースだけですみます。 でもテンプレートだと、Baseクラスのヘッダを書き変えることになるので それをインクルードしてるソースは全部再コンパイルの必要が生じます。 「まぁそんな頻繁に目的の構造体は仕様変更されるわけじゃないだろうから良い」なら良いですが また Aに当たる部分をテンプレート化する場合は、SetAやFreeABuf関数など部分を、対応できるようにしとかないといけません。 「テンプレートや関数オブジェクトや関数ポインタの仕様には強い」という自身があれば構いませんが、かなりの技量だとしても結構注意力を要することは確かですね。 これらを踏まえてなお それでいいという明確な理由があるなら、そうしても問題ないと思います。

回答No.27

>再ビルドは必要になります。 もちろん、再ビルドは必要です。 それはkatorea21さんも十分承知なんではないでしょうか?(言葉のあや?) んでもその時に再コンパイルする必要のある箇所がどの程度大きいかは コードの書き方一つで 全然変わってきます。 あ、一応コードで書いてはなかったので >受け渡しするデータ型に値型のデータフィールドしかなければ、ただ単純に=でコピーできますが、どうしても動的にメモリ確保してデータコピーしてやらなければならない場面(ポインタ型フィールド)があるのです。 ↓構造はこんな感じですかね ※実際には分割しておいてください。また、伝えたいのは「構造」の方で、長くなりすぎるのはあれなので、呼び出し側の「単なる簡易実験」コードは非常に分かり辛くなっていますが、実際に「送り手がfreeする部分」については書く必要がないはずですし、実際のコードでは可能な限り分かりやすいように配慮しておいてください。 struct A{ //目的のPODとする double d; void* buf; //確保したメモリ size_t bufsize; //確保したメモリ量 int i; }; class Base { //Aのコピーや内部のポインタの管理は基底クラスが行う A* pa; void FreeABuf() const { free( pa->buf ); pa->buf = NULL; } protected: Base(){ pa = new A; pa->buf = NULL; } public: virtual ~Base(){ FreeABuf(); delete pa; } //このSetAを使うだけで、Aのコピーは出来てしまう。 void SetA( const A* a ) const { FreeABuf(); *pa = *a; pa->buf = malloc(a->bufsize); memcpy( pa->buf, a->buf, a->bufsize ); } const A* GetA() const { return pa; } virtual void Func() = 0; }; class Sub1 : public Base { int i; public: Sub1( const A* a ){ SetA(a); } virtual void Func() override { printf( (char*)GetA()->buf ); } }; /////////////構造ここまで。ここからは呼び出しコード(実験)////////////////// int main(void){ A a1 = { 1.0, NULL, 100, 3 }; a1.buf = malloc(100); strcpy( (char*)a1.buf, "abc " ); Base* b = new Sub1( &a1 ); free(a1.buf); //ここで解放されても問題なし b->Func(); a1.buf = "zzz "; a1.bufsize = strlen("zzz ") + 1; b->SetA( &a1 ); b->Func(); delete b; } 結果 abc zzz 実際には Sub1 b2: b2 = *b; こんな風に、「自分の方でもう一個さらに余分に作る」必要はさすがにないでしょうが 万が一必要になったら Sub1に用意したoperator =のなかで 「SetAを呼び出してから」 派生クラスのメンバは別途コピーすればいい、というだけになります。 また もし派生クラスに「メンバ変数」 が必要なければ 継承はなしで、Baseクラスだけで switch文とかでも事足りるかもしれません。 どっちがより良いかは状況によります。

katorea21
質問者

お礼

再ビルドは少なくとも必要でした。言葉が間違っていました。 言いたかったのは、自作のクラスに手を入れる必要がないと言うことです。これはこれで大きいのです。再ビルドだけなら誰でも出来ますが、コードの修正は分かっている人にしか出来ませんから。 コード例かなりシンプルに分かり易く書いて頂いてありがとうございます。ほぼイメージ通りです。 ここまで来ると、Baseはテンプレートクラスにして「A」と言う型を意識しないような作りにしておく方が良いような気もします。(テンプレートというかインタフェースクラス?)

  • wormhole
  • ベストアンサー率28% (1626/5665)
回答No.26

>その通りです。 「memcpyにしておけば~再ビルドの必要はない」と本当に思われてるのでしたら それは気のせいです。 >memcpy((HOGE*)this, &obj_HOGE_Derived, sizeof(HOGE)); という記述をしている限り再ビルドは必要になります。 sizeofはコンパイル時点のサイズを求める演算子ということを理解されてますか? またクラスにしても基底クラスが変更になったのであれば、 その派生クラスの再ビルドは必要です。 memcpyを使用することで再ビルド、テスト、出荷作業をなくしたいようですが それはなくしたらいけません(なくせません)。面倒がらずにしてください。

katorea21
質問者

お礼

>sizeofはコンパイル時点のサイズを求める演算子ということを理解されてますか? それは、実行時に処理系に応じて必要なメモリサイズを動的に算出して返す演算子だと思っていました。改めて調べて見ると、コンパイル時に固定値になるような事を書いてありました。 となると、コンパイル時の環境に依存した値になるという事ですか?コンパイル時にsizeof(int)が4と評価される環境でコンパイルした実行モジュールを、int型が2バイトの環境に持って行って実行したら、上記のようなコードではその環境における実際のサイズ分コピーできませんね。Javaのように動的にクラスロードするわけではないから、コンパイル時にクラス型のサイズも確定していて、それが固定値として実行モジュールに書き込まれてしまうと言うことですね?

回答No.25

>外部にデータを受け渡す時に、別途メモリを確保してそこにコピーし、渡した先はデータを受け取った後、そのエリアをfreeします。外部とのインタフェースに関しては、そのような仕様になっているので、勝手に変更は出来ません。 なるほど、そういうことですか。 もし「派生クラスにメンバ変数を追加しなくていい」 状況であるならば この状況下では、やはり基本的な処理に関しては そのPOD構造体を扱うための、ラッパーとしての、基底クラス(抽象クラス)に包含で含んでおいて もうちょっと具体的な処理は派生クラスで切り分ける、といった形にしておき 包含しているPODのコピーに関しては 一括=やmemcpyなどがベストな感じに思います。 リファクタリングした方が良さそうな状況だと判明した場合は 既に大量のコードを書いてらっしゃるのでしたら 大変かもしれません。 そこはもう「頑張ってください」、と言うしかありませんが 何年か先になった時に、その見返りは結構なものになる可能性は十分あると思いますから ここでまだ公開されていない現在の状況含め、じっくりとご検討してみてください。

回答No.24

>「基本クラスに変更が入ってもmemcpyにしておけば、 >ポインタの追加でもないかぎりmemcpyの部分のコードに修正しないので >再ビルドの必要がなくなり、 >結果それに伴うテストや出荷などの事務手続きも発生しない」って >ことになりそうなのですが、あってます? 包含であれば、その部分(POD)に対してmemcpyや一括で=でOKですが 継承だと=等で毎回メンバ毎に書くことに で、私(や先人)のお勧めとしては「継承は最小限のが良いんでない?」(そうすりゃ再コンパイルの手間も最小限だし) という流れだと思いますよw

katorea21
質問者

お礼

ご教示頂いている意味がだんだん理解できてきました。 もう一度、元々の目的に立ち返って考え直してみたいと思います。 色々とアドバイスありがとうございました。

回答No.23

>外部から受け取ったデータをクラスで保持する必要があるのです。 なるほど、そのデータはその後「外部から破壊される、あるいは外部から書き換えされる」ものですか? そうであればコピーは確かに必要かもしれません。 でも継承とかの構造に関しては…… 「結合を最小にせよ」というのは、実は私だけが自分の経験からだけで言ってるんじゃなくて 一般的な、C++にディープに触れてる本には、結構書いてあるようなことなんじゃないでしょうか 例えば、今私の手元にある More Exceptional C++(ハーブ・サッター 著 浜田 真理 訳 浜田 光之 監修) とかにもふつーに書いてあります。 p157,p158から少し引用させていただきますと ------------- 以前にも主張したように、経験を積んだ開発者でさえ、継承を使いすぎる傾向がある。ソフトウェアエンジニアリングの健全なるルールは、「結合を最小にせよ」だ。複数の方法でクラス間の関係を表現できる場合、最も弱く、かつ実用上十分な関係を採用しよう。継承は、C++のクラス間の関係としてはほとんど最強であり、これより強い関係はfriendしかない (以下略) 弱い結合は、(例外安全も含め)プログラムの正しさを向上させる。そして、強い結合は、(例外安全も含め)プログラムの正しさの到達可能レベルを下げる。 ------------- こんな感じですよね。 作ってる最中はいいかもしれませんが、ずっと後で見た時や、別の人が見た時 継承使いまくりだと 派生クラスのメンバは、全体として何があるのか って把握するのも苦労しますし 基底クラスに変更があると、全部コンパイルしなおしになりますし… 包含にしとけば、「例外安全にもしやすい」ですから エラーとかに強い構造、あとで機能拡張、バグフィックスがしやすいようにしっかり組み上げようとすると 結局、一部で「最初は」多少長く感じても 総合的に見たときに包含の方がシンプルになりやすい、という感覚です。 もちろん、「意味的に継承が最適」なのであればこの限りではありませんが。

katorea21
質問者

お礼

外部にデータを受け渡す時に、別途メモリを確保してそこにコピーし、渡した先はデータを受け取った後、そのエリアをfreeします。外部とのインタフェースに関しては、そのような仕様になっているので、勝手に変更は出来ません。 継承より包含、に関してはもっと専門書等を当たって調べたいと思います。 ただ、今回の場合は、あくまで内部処理用のユーティリティ的な機能を追加したかっただけの継承なので、包含でも確かに問題はないと思います。 アドバイスありがとうございました。

  • wormhole
  • ベストアンサー率28% (1626/5665)
回答No.22

>はい、作業自体はそれほど時間のかかるものではありませんが、製品ですのでテストや出荷等の事務手続きも伴うためなるべくそういったことは発生しないようにしたいと言うことです。 今までの流れからすると、これって 「基本クラスに変更が入ってもmemcpyにしておけば、 ポインタの追加でもないかぎりmemcpyの部分のコードに修正しないので 再ビルドの必要がなくなり、 結果それに伴うテストや出荷などの事務手続きも発生しない」って ことになりそうなのですが、あってます?

katorea21
質問者

お礼

その通りです。

回答No.21

>こうなっていた場合、aにアクセスするためには、包含を使用する場合、_Bobj.pB->objA.pA->aのような書き方になると思います。 setter/getterを間に挟めば、ますます冗長なコードになります。 それは「概念的に、絶対継承しなくちゃいけない」類の物なのでしょうか? もし「継承する『必然性』」がないのであれば、冗長でもsetter/getterを挟むほうが「後々遥かに楽に」なりますし、直交した概念のものであれば縦のつながりは少なく、横方向に広げられるはずです。 「多重継承」もそうですが「多段な継承」もまた、しすぎるのはお勧めできません。 あるいは「別途データ受け渡し用の構造体」を作って、Getは一本化してしまうという手もあります。 というより、本当にそんなに「継承する必要のあるクラス」 であるなら、インスタンスのコピーをとらないことに集中するのが本筋です。 コピーがぜーーーーーーったいにいるとしたら それは 「全く同じデータ群を持つ巨大なものが2つ以上存在する瞬間がないとぜーーーーーーったいに困る」 というケースのみです。 本当に2つ以上存在する瞬間がないとぜーーーーーーったいに困る状況ですか?

katorea21
質問者

お礼

外部から受け取ったデータをクラスで保持する必要があるのです。これを、内部的に扱いやすいように派生させた型のインスタンスとして保持します。つまり、コピー(代入)が発生します。しかも、基本クラス型→派生クラス型への代入(コピーコンストラクタ)となります。 またその逆もあります。この場合は、派生クラス型→基本クラス型へのコピーとなります。 受け渡しするデータ型に値型のデータフィールドしかなければ、ただ単純に=でコピーできますが、どうしても動的にメモリ確保してデータコピーしてやらなければならない場面(ポインタ型フィールド)があるのです。