- ベストアンサー
ポインタのキャストについて
- ポインタのキャストについて説明します。HOGEクラスとHOGE_Derivedクラスについてのコード例を使用して、メモリ上のデータの並びについて説明します。
- また、HOGE_Derivedクラスで演算子のオーバーロードを行っているコードについても説明します。
- memcpyを使用したコピーの方法について処理系の依存性や自然さについても考察しています。
- みんなの回答 (31)
- 専門家の回答
質問者が選んだベストアンサー
C++(Cの範囲まで含めると)は相当に複雑で また自由度が高い分プログラマの理解と注意力を要求する言語です。 従って 言語仕様とプログラミングの手法、設計全て込みになってくると、もはや 「知らない、分からない事がある方が遥かに普通」と思った方が良いですね。 考えようによってはプログラミングも芸術の一種です。 基本的なことほど、「調べないので逆に案外気づかない」とかいう事もあります。 世の中のC++のプラグラマー全体の内、今回の質問・回答で出てきたこと全ての内容について、なにも参照し直さず すらすら答えられる人など、1%もいないんじゃないかという気がします。 なので、もし今後分からない事が出てきたりしたら、どんどん聞いたり調べたりしてみてください。 そうした方が後で自分が助かります。 まぁ、今回の内容に関してはおそらくほとんど網羅できたんではないかと思いますし、よほどの事がない限り私はここいらで「回答サイド」は一時失礼する、かもしれませんw 何しろ、そろそろ自分のプログラムの方に集中すべき時間になってきたっぽいのでw 数年がかりの物ですが、まさにそろそろクライマックスか!?って感じです。 そんではノシ
その他の回答 (30)
- LongSecret
- ベストアンサー率68% (22/32)
>しかし、その包含したインスタンスでは、基本クラス(実際は構造体ですが)のフィールドにアクセスするためににいちいちgetter/setterを用意するか、aをpublicにして、b.a->dのようにアクセスしなければならないのです。 >まあそうすれば良いだけのことですが、何となくスマートではないなと思ったのと、いわゆるオブジェクト指向的なやり方ではないため、継承を使った構造にしたわけです。 ん~ 「100%の理想の、万全を尽くせるか?」 という観点では、確かにそうなんですよねw 目的のPOD構造体が「自分でいじれるなら」 んでも、「PODの構造体」を、「ある程度別の物」(intなどの単純な値型と同じように考える)と割り切ることによれば、十分オブジェクト指向的だと思いますよ。 下のコードだと、ポイントはクラスBの中の const A* GetA() const { return a; } このメンバにあります。 constなポインタとしてAを公開してるだけなんで クラスB自体はAの内容は、これだけなら知らなくてもいいわけです。 そんで このGetAの呼び出し側で B b(何々); const A* a = b.GetA(); とかやってれば a->メンバ ってな具合で、constに読み取りができます。 で、もし「POD構造体をもう一度」Setしたいんであれば void SetA(const A& a_){ *a = a_; } こんなコードを クラスBに追加してやればいいわけです。 単純ですよね? (もちろん、Aのメンバにポインタがあって、そのディープコピーも行うべきであればもう一行二行程度書く必要はありますが、「それだけである」とはいえます)
- LongSecret
- ベストアンサー率68% (22/32)
失礼、「コメント部分」がまだミスってました B& B::operator=(const B& b){ *a = *b.a; //あるいは memcpy( a, b.a, sizeof a ); return *this; } ↓ B& B::operator=(const B& b){ *a = *b.a; //あるいは memcpy( a, b.a, sizeof(A) ); return *this; } また、PODの構造体を受け渡せるなら、コンストラクタは B::B(double d, int i){ a = new A; a->d = d; a->i = i; } ↓ B::B( const A& a_ ){ a = new A(a_); } のがさらに構造体メンバへの依存度が低いため、良いと思います。 実際にはやはり分割した方が再コンパイルの手間は少ないですが 仮に一つの個所にまとめると こういう感じで良いんではないでしょうか? struct A{ double d; int i; }; class B{ A* a; public: B(const A& a_ ){ a = new A(a_); } B& operator=(const B& b) { *a = *b.a; return *this; } const A* GetA() const { return a; } }; A a = { 2.0, 9 }; A a2 = { 0, 0 }; B b1( a ), b2( a2 ); b2 = b1; printf("%f %d",b2.GetA()->d, b2.GetA()->i);
- LongSecret
- ベストアンサー率68% (22/32)
コピペミスです 下のほう B& B::operator=(const B& b){ *a = *b.a; //あるいは memcpy( a, b.a, sizeof a ); } ↓ B& B::operator=(const B& b){ *a = *b.a; //あるいは memcpy( a, b.a, sizeof a ); return *this; }
- LongSecret
- ベストアンサー率68% (22/32)
う~ん 私の「継承ではなく包含することについての提案」に対してノータッチですが 理解されてますか? >メンバの並びについての話がまさに質問したかった事なんですが、やはり処理系に依存する可能性もあると言うことで、このようなケースでmemcpyは使用しないようにしようと思います。ただ純粋にPOD型の構造体同士のコピーなら、つまりC言語と互換なのでmemcpyが使えると思います。 継承してフィールド追加されたりすると(PODじゃなくなると)メンバの並びの話も出てくるので当然ダメですが、継承じゃなくて包含してれば、そのメンバについてはPODになるのでそこは=やmemcpyで一発で行けるんですが… //POD struct A{ double d; int i; }; class B{ //非POD A a; public: B(double d, int i){ a.d = d; a.i = i; } B& operator=(const B& b) { a = b.a; //あるいは memcpy( &a, &b.a, sizeof a ); return *this; } }; B b1( 2.0, 9 ); B b2( 0, 0 ); // b2.a.d == 0 , b2.a.i == 0 になる b2 = b1; // b2.a.d == 2.0, b2.a.i == 9 になる >基本クラス(構造体ですが)は、5年程度のスパンで見れば割と仕様変更は発生します。またそのような構造体が何十とあります。フィールド一つの変更でも全てビルドし直すのは、結構な手間とコストになるのです。もちろんポインタ変数が追加されればディープコピー処理を追加しなければならないですが。 構造体が自分では変更不可で、かつ変わる可能性がある のなら、「ある程度は仕方ない」ですが 継承ではなく包含になっていれば、たとえ構造体のフィールドが変わったところで 「ポインタ変数が追加されて、しかもその構造体をクラスに対して一つずつ持ってる状況で、しかもクラス全体のコピーが必要になった状況で、しかもそれの参照先のディープコピーも必要になった場合」 という、「整理されたデザインパターン」上で考えればきわめて稀なケースでなければ、上記のとおり=やmemcpyで書いておくことで ほとんど書き変えずにリビルドすればいいだけです。 まして「ポインタによる包含」になっていれば メンバに直接触れてない部分は再コンパイルの必要もないですから 数個のソースぐらいの再コンパイルぐらいしかほとんど手間はないのです。 //Aのクラス定義が書かれてるヘッダはこの時点ではincludeしない struct A; class B{ //非POD A* a; public: B(double d, int i); ~B(); B& operator=(const B& b); }; ///Aが絡むところは、ソースでインクルードしておいてから、ソースに実装を書く/// B::B(double d, int i){ a = new A; a->d = d; a->i = i; } B::~B(){ delete a;} B& B::operator=(const B& b){ *a = *b.a; //あるいは memcpy( a, b.a, sizeof a ); } こうしておけば、このソースを再コンパイルする必要があるだけで 「Bのヘッダ」をインクルードしたソースは無関係です。
お礼
包含ですか。確かにそのようなデータ構造も考えはしました。 しかし、その包含したインスタンスでは、基本クラス(実際は構造体ですが)のフィールドにアクセスするためににいちいちgetter/setterを用意するか、aをpublicにして、b.a->dのようにアクセスしなければならないのです。 まあそうすれば良いだけのことですが、何となくスマートではないなと思ったのと、いわゆるオブジェクト指向的なやり方ではないため、継承を使った構造にしたわけです。
補足
さらに補足しますと、最初にサンプルで挙げたクラスは、中に他のクラス(構造体)型のフィールドが含まれていませんが、実際は他のクラス型(もちろん自作型です)のフィールドが複数含まれています。そして、そのクラスも元の構造体から派生したクラス型を使いたいのです。 包含の考え方ですと、包含したクラス自体がまたさらに別のクラスを包含した型となっており、継承の考え方を使わなければ、入れ子になったクラスのフィールドにアクセスするのにいくつものオブジェクトを経由するような形になり、見た目的にも良くないし、ぱっと見で人間の頭で素直に理解しにくいコードになってしまうような気がします。 class A: { int a; } class _A { A* pA; } class B: { int b; A objA; } class _B { B* pB; } _B _Bobj; こうなっていた場合、aにアクセスするためには、包含を使用する場合、_Bobj.pB->objA.pA->aのような書き方になると思います。 setter/getterを間に挟めば、ますます冗長なコードになります。
- wormhole
- ベストアンサー率28% (1626/5665)
>フィールド一つの変更でも全てビルドし直すのは、結構な手間とコストになるのです。 「修正するのに手間とコストがかかる」ではなく 「ビルドし直すの手間とコストがかかる」なのですか? Makefileなどをちゃんと書いているなら makeを起動しコンパイルが終わるのを待つだけで 手間もコストもないと思うのですが・・・ memcpyでもビルドし直さないといけないのは同じだし。
お礼
はい、作業自体はそれほど時間のかかるものではありませんが、製品ですのでテストや出荷等の事務手続きも伴うためなるべくそういったことは発生しないようにしたいと言うことです。
- LongSecret
- ベストアンサー率68% (22/32)
まぁ、一つのPODの構造体から、同じ型の一つの構造体 での話なら 変数名 = 変数名; でも最適化で同じコードになるかもしれませんし、違うコードが生成されたとしても、計測上の誤差のが勝る事のが遥かに多いんじゃないでしょうか。 memmoveやmemcpyはどっちかっていうと「配列とループ」のかわりに使われることの方が割合はずっと多いんじゃないでしょうか。 「VC++7.1では,PODのポインタに対するcopyはmemmoveで実装されています.」 とかCryoliteさんの日記で見た気がしますが STL使うと その辺意識しなくても良いって言う意味はあると思います。 自分の手では「全く使うコードを書かない」でも、プログラムが書けないわけじゃないです。 (まぁ、あくまで「もしコピーをやんなくて済む手がある状況だったら、それがいっちばん速い」ですw memmoveやmemcpyは最適化が許されてますし、それは相当なものですが 「何もやらない」には確実に勝てませんw)
- wormhole
- ベストアンサー率28% (1626/5665)
>「構造体のサイズ」だけ意識してmemcpyで構造体全体をコピーするようにしておけば、構造体の定義が変わっても修正は不要となります。 だから、それを「省略することに異常にこだわっている」といってるんですけどね。 構造体に追加されるのがポインタ変数だと修正不要ともいえないでしょうし。 正直、私はmemcpy、Cでも文字列やバイナリデータのコピーくらいにしか使わないけど。
お礼
本ケースで確実な方法は、単純にメンバ個別に=で代入することだと分かりましたので、memcpyは使用しないようにしたいと思います。色々とありがとうございました。 ただ補足するなら、基本クラス(構造体ですが)は、5年程度のスパンで見れば割と仕様変更は発生します。またそのような構造体が何十とあります。フィールド一つの変更でも全てビルドし直すのは、結構な手間とコストになるのです。もちろんポインタ変数が追加されればディープコピー処理を追加しなければならないですが。
- Tacosan
- ベストアンサー率23% (3656/15482)
ぶっちゃけた話, C でもそれほど memcpy が必要とは思えない. 単に代入で書いておいて, コンパイラにまかせた方がいいと思うけどねぇ. 配列データのコピーだって std::copy を使う手もある. あれ? よく考えてみたら lpszStr = NULL; ってアウトじゃない? このメンバは HOGE から継承してるんだけど, HOGE で (デフォルトの) private とされているのでアクセスできないはず.... あと, 最後のメンバの並びについては「それぞれのクラスでかたまっている」ように配置しないと困りますよね. 配置する順序は決まっていないと思ういます. つまり, コンパイラによっては「基本クラス→派生クラス」かもしれないしその逆かもしれない.
お礼
ご回答ありがとうございます。 元のクラスHOGEは実際は構造体でした。説明のために多少改変しましたが、lpszStrのディープコピーの部分は質問の本質ではありませんので、適当に書いてしまいましたm(_)m メンバの並びについての話がまさに質問したかった事なんですが、やはり処理系に依存する可能性もあると言うことで、このようなケースでmemcpyは使用しないようにしようと思います。ただ純粋にPOD型の構造体同士のコピーなら、つまりC言語と互換なのでmemcpyが使えると思います。
補足
>ぶっちゃけた話, C でもそれほど memcpy が必要とは思えない. 単に代入で書いておいて, コンパイラにまかせた方がいいと思うけどねぇ 単なる値型のデータならそれで良いし、こんなに悩むことはないと思います。 ただ単純に=で代入すれば良いだけの話しです。 コンパイラがデフォルトの=演算子を定義してくれて、各メンバが全てコピーされるわけですね。しかし、ポインタが含まれているとそうはいかない。そのため=演算子をオーバーロードするわけですが、この中で第二オペランドを=で第一オペランドに代入することは出来ません。(無限ループになる) なので、内部を直接コピーしてやる必要があるのですが、その手段が正しいかどうかの質問でした。
- LongSecret
- ベストアンサー率68% (22/32)
>>それ以外のオブジェクト型をmemcpyでコピーするには常に危険がつきまとうと言う感じでしょうか。 >はい、そう理解しておくのが確実ですね。 おっと、一応 今までのやりとりから、書く必要はないとは思いますが、PODでありさえすればmemcpyやmemmoveなどOKです。
- LongSecret
- ベストアンサー率68% (22/32)
>ただ、基本クラスは外部が設計したクラスで、これを変更することは出来ません。 な~るほど。 >なので、それを内部的に使い勝手の良いように派生クラスを作成したと言うわけです。 派生クラス同士、及び基本クラス⇔派生クラスのコピーは必要になる場面があるのです。 ホントにダメですか?(w 包含じゃ 例) struct A{int i;}; //こいつが目的のPODとして class Base { //別途抽象クラス protected: A a; //または A* a; public: virtual ~Base(){} const A* GetA() const { return &a; } virtual void CopyTo(Base*) const = 0; }; class Sub1 : Base { virtual void CopyTo(Base*) const override { 何々; } }; >それ以外のオブジェクト型をmemcpyでコピーするには常に危険がつきまとうと言う感じでしょうか。 はい、そう理解しておくのが確実ですね。
お礼
ご自身の作業時間を削ってまでお付き合い頂き、大変ありがとうございました。 改めて基本的な部分から見直すきっかけとなり、大変有意義なQ&Aとなりました。 ベストアンサーは決めにくいので、とりあえず本回答をベストアンサーとして、質問を締め切らせて頂きます。 ご回答頂いた全ての皆様、ありがとうございました。