- ベストアンサー
ポインタのキャストについて
- ポインタのキャストについて説明します。HOGEクラスとHOGE_Derivedクラスについてのコード例を使用して、メモリ上のデータの並びについて説明します。
- また、HOGE_Derivedクラスで演算子のオーバーロードを行っているコードについても説明します。
- memcpyを使用したコピーの方法について処理系の依存性や自然さについても考察しています。
- みんなの回答 (31)
- 専門家の回答
質問者が選んだベストアンサー
C++(Cの範囲まで含めると)は相当に複雑で また自由度が高い分プログラマの理解と注意力を要求する言語です。 従って 言語仕様とプログラミングの手法、設計全て込みになってくると、もはや 「知らない、分からない事がある方が遥かに普通」と思った方が良いですね。 考えようによってはプログラミングも芸術の一種です。 基本的なことほど、「調べないので逆に案外気づかない」とかいう事もあります。 世の中のC++のプラグラマー全体の内、今回の質問・回答で出てきたこと全ての内容について、なにも参照し直さず すらすら答えられる人など、1%もいないんじゃないかという気がします。 なので、もし今後分からない事が出てきたりしたら、どんどん聞いたり調べたりしてみてください。 そうした方が後で自分が助かります。 まぁ、今回の内容に関してはおそらくほとんど網羅できたんではないかと思いますし、よほどの事がない限り私はここいらで「回答サイド」は一時失礼する、かもしれませんw 何しろ、そろそろ自分のプログラムの方に集中すべき時間になってきたっぽいのでw 数年がかりの物ですが、まさにそろそろクライマックスか!?って感じです。 そんではノシ
その他の回答 (30)
- LongSecret
- ベストアンサー率68% (22/32)
>しかし、そうするとC++でmemcpy関数の出番はほとんどないのではないでしょうか? ある型がPODでなくても「ある型のポインタ」はPODですから ポインタの配列だったら移せますし(これが超便利なこともありますし、これだけでもかなりの事が出来ます) そうでなくとも巨大な配列をコピーする必要がある場合だと重宝する可能性があります。 というより、「C++11でわざわざPODの定義の見直しがされた」 ことからもわかるように、パフォーマンスが要求される場面や 標準的な機能を提供するライブラリ(や、それを自作する場合)などでは 十分使える機能です。 もちろん、「コピーその物を最小限」である方が良いプログラムですが、これはその上でさらにもう一歩踏み込む場合に使う、ということです。
お礼
なるほど、そういうことですね。 いずれにしても、単純な値型(ポインタ型も含む)を大量にコピーする際には使えるが、それ以外のオブジェクト型をmemcpyでコピーするには常に危険がつきまとうと言う感じでしょうか。
- wormhole
- ベストアンサー率28% (1626/5665)
>しかし、そうするとC++でmemcpy関数の出番はほとんどないのではないでしょうか? はい、出番はほとんどないと思います。 Cとの互換性のために残ってるようなものと思ってよいかと思います。
- wormhole
- ベストアンサー率28% (1626/5665)
>もう一つ、何度も質問して申し訳ありませんが、最初の質問のサンプルコードで、基本クラス(HOGE)が、クラスではなくPOD型の構造体であったとします。(プリミティブ型の他、ポインタ型も含みます) (以下略) POD型であるなら問題ないと思います。 私ならmemcpyより可能な限り=使いますけどね。 ただまぁ・・・スマートというより、コードの記述量を減らす(省略する)ことに異常にこだわってるだけのように思えるんですけど。 私としてはスマートな記述というのは省略することではなくて 無駄なコードがないことだと思いますが。
お礼
なるほど、確かに=でコピーした方が確実は確実でしょうね。 ただし、メリットはコードの記述量を減らすだけではありません。 メンバ一つ一つを意識したコピーをすれば、メンバが追加になった時はコードの修正が必要になります。 「構造体のサイズ」だけ意識してmemcpyで構造体全体をコピーするようにしておけば、構造体の定義が変わっても修正は不要となります。
- LongSecret
- ベストアンサー率68% (22/32)
う~ん >POD かどうかによらず, 自信をもって大丈夫といえる状況でなければ memcpy は避けるべきじゃないかなぁ. ですよねぇw といって独自operator = を実装したらその段階でPODじゃなくなっちゃうし 緩和されたC++11でさえもこんだけの制限があります http://ja.wikipedia.org/wiki/C%2B%2B11#Plain_Old_Data_.E5.9E.8B.E3.81.AE.E5.AE.9A.E7.BE.A9.E3.81.AE.E4.BF.AE.E6.AD.A3 >メンバ関数 は持っててもいいですが ユーザー定義のコンストラクタ持てません。 仮想関数持てません。(デストラクタも当然、virtualでも独自定義でもいけない) 全ての非静的メンバが、同じアクセス制御じゃないといけません。 従って >外部とのやりとりは基本クラス型のインスタンスで行う必要がある という状況で、しかも基底クラスがPODってことになってると 「本当にどうしても、派生したインスタンスのコピーを行う必要があるかどうか」(参照やポインタだけで済ませないか) とか 現在の構造が「継承の旨みを十分に生かせてないんではないか」というところの方を疑った方が良いように思います。 あるいは「継承じゃなく包含の方が適切ではないか?」という予感もなくはないです。 それらを踏まえてどうしても現状の方法がベストで、operator=が必要なのであれば 全体でのmemcpyなどはあきらめて、ポリモーフィズムやアクセス修飾子の強化といった、継承の旨みを十分に生かすというアプローチで「全体のコードをスマートにする」方が無難だし、確実だし、言語仕様に対して素直な方法に思います。
お礼
おっしゃるとおりですね。 ただ、基本クラスは外部が設計したクラスで、これを変更することは出来ません。 なので、それを内部的に使い勝手の良いように派生クラスを作成したと言うわけです。 派生クラス同士、及び基本クラス⇔派生クラスのコピーは必要になる場面があるのです。
- Tacosan
- ベストアンサー率23% (3656/15482)
POD かどうかによらず, 自信をもって大丈夫といえる状況でなければ memcpy は避けるべきじゃないかなぁ. 少なくとも, 親クラスに operator = を実装し, 子クラスからはそれを呼び出すようにすれば責任は回避できるよね.
補足
しかし、そうするとC++でmemcpy関数の出番はほとんどないのではないでしょうか? プリミティブ型であれば普通に代入で済みますし、文字列はstrcpyでコピーできます。 プリミティブ型の配列のコピーくらいでしょうか。
- wormhole
- ベストアンサー率28% (1626/5665)
>代入演算子をオーバーロードするよりは、Copy関数のようなものを作って、その中で(コンパイラがデフォルトで作成する代入演算子関数によって)通常の代入を行った上で、ポインタ部分のディープコピーを行う形にした方が良いのでしょうか? 私がもしやる場合ですが、 classとして定義するなら代入演算子をオーバーロードして 面倒でもメンバー一つづつコピーします。 structで定義するならPOD型になるようにした上でメンバは 利用する側からはわからないように必ずポインタで操作するようにし、 そのためのコンストラクタ、デストラクタ、代入演算子に対応する関数を用意します。 >クラスではなく構造体なら上記のようなmemcpyによる代入は問題ないのでしょうか? >C++の場合、クラスも構造体もほぼ同じだと思うのですが。 >違いはデフォルトのアクセス修飾子くらい? POD型を調べれば、おのずとわかりますよ。 私が「POD型」という言葉を知ったのも、この質問に回答してなので 調べるのに30分もいらないと思います。
お礼
POD型調べてきました。 結局メンバ関数が含まれていればPOD型ではないと言うことなのですね。 この場合は構造体でもmemcpyは出来ないと。 もう一つ、何度も質問して申し訳ありませんが、最初の質問のサンプルコードで、基本クラス(HOGE)が、クラスではなくPOD型の構造体であったとします。(プリミティブ型の他、ポインタ型も含みます) 派生クラスのインスタンスを基本クラスのインスタンスにキャストした上で、sizeof(HOGE)バイト分ををmemcpyする分には問題ないのでしょうか? その上で、派生クラスで追加定義したフィールドを個別に手動でコピーすれば、比較的スマートになると思いました。(やはりメモリ上の物理的な並びが問題になる?) そもそも上記サンプルは説明のために多少改変しましたが、実際にはHOGEはPOD型の構造体なのです。この型を使いやすいようにするために、内部処理用として派生クラスを作成してメンバ関数を追加したのでした。ところが、外部とのやりとりは基本クラス型のインスタンスで行う必要があるため、型変換用にoperator=のオーバーロードが必要なのでした。
- wormhole
- ベストアンサー率28% (1626/5665)
>インスタンスコピーにmemcpyを使ってはいけない理由は何でしょうか? >クラスに仮想関数は使用していないため、仮想関数ポインタ等の隠しフィールド(?)が挿入されていることもないと思います。 仮想関数がないからといってCの構造体と互換性があるとはいえないからです。 「POD型」を調べてみてください。 そしてその「POD型」の定義もC++03とC++11では異なります。 >フィールド数が何百もある場合、その一つずつをコピーするコードを書くのは大変だと思うのですが。 そのクラス設計自体がおかしい。
お礼
POD型、調べてみます。 やはりクラスのコピーにmemcpyは使わない方が良いと言うことですね。 =演算子をオーバーロードして、ポインタを含むクラスのコピーをスマートに行おうとしたのですが。 代入演算子をオーバーロードするよりは、Copy関数のようなものを作って、その中で(コンパイラがデフォルトで作成する代入演算子関数によって)通常の代入を行った上で、ポインタ部分のディープコピーを行う形にした方が良いのでしょうか? この場合は、=演算子は使えない(使うといわゆるシャローコピーになってしまう)ため、使わないようにしなければなりませんが。 ちなみにここに似たような質問がありました。 http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1339047348
補足
もう一つお聞きして良いでしょうか? クラスではなく構造体なら上記のようなmemcpyによる代入は問題ないのでしょうか? C++の場合、クラスも構造体もほぼ同じだと思うのですが。 違いはデフォルトのアクセス修飾子くらい?
- kmee
- ベストアンサー率55% (1857/3366)
一番の違いは「コンストラクタが呼ばれない」でしょう。 > 気になるのは、HOGE_Derivedクラスのオブジェクトのメモリ内のデータの並びがどのようになっているのかと言うことです。 そういうのが気になるのなら、なおさらmemcpy使わない方がいいでしょう。 どう配置するか、最終的には処理系に依存することですから。 > フィールド数が何百もある場合 これは、また別の問題。そんな無茶な設計自体を見直すのが先です。
お礼
ご回答ありがとうございます。 コード例には省略しましたが、operator=演算子のオーバーロードの中でデストラクタを呼んでいます。内部を一旦クリアしてから、引数(第2オペランド)のデータで初期化しているわけです。 >そういうのが気になるのなら、なおさらmemcpy使わない方がいいでしょう。 どう配置するか、最終的には処理系に依存することですから。 やはりそうですか。要素一つずつコピーしてやるのが確実と言うことですね。 フィールド数に関しては、何百というのは極端でしたm(_)m でも実際10以上はあるので、一つずつ=で代入するのもスマートではないと思いました。
- wormhole
- ベストアンサー率28% (1626/5665)
>インスタンスをmemcpyしているのではなく、インスタンスをコピーする演算子のオーバーロード関数でmemcpyした後、ポインタ部分をディープコピーしようとしています。 代入演算子のオーバーロード関数内だろうが、そうでなかろうが インスタンスをmemcpyしてるのには変わりないです。 >こういう場合は、memcpyではなく、各フィールドを一つずつ代入してやらなければならないのでしょうか? そういうことです。 memcpyしてよいのはプリミティブ型(char,intなど)だけと思ってください。
お礼
インスタンスコピーにmemcpyを使ってはいけない理由は何でしょうか? クラスに仮想関数は使用していないため、仮想関数ポインタ等の隠しフィールド(?)が挿入されていることもないと思います。 外部で直接memcpyを行わないように、=演算子をオーバーロードして、内部的にmemcpyした後、間接参照部分(ポインタ)はディープコピーし、参照先を書き換えています。これでは安全ではないと言うことでしょうか? フィールド数が何百もある場合、その一つずつをコピーするコードを書くのは大変だと思うのですが。
補足
念のため補足しますが、ここで定義しているクラスは完全自作のクラスです。また、中身を知らないオブジェクト型のフィールドはありません。プリミティブ型、ポインタ型、及び自作の他のクラス型のみです。もちろんそのクラスの中身も上記と同様です。 ポインタ型のフィールドに関しては、既に説明済みの通り、一旦memcpyにより値がコピーされますが、直後に参照先をコピーした上でそちらを参照するように書き換えています。
- wormhole
- ベストアンサー率28% (1626/5665)
インスタンスのコピーにmemcpyを使ってはいけません。
お礼
ご回答ありがとうございます。 インスタンスをmemcpyしているのではなく、インスタンスをコピーする演算子のオーバーロード関数でmemcpyした後、ポインタ部分をディープコピーしようとしています。 こういう場合は、memcpyではなく、各フィールドを一つずつ代入してやらなければならないのでしょうか?
お礼
ご自身の作業時間を削ってまでお付き合い頂き、大変ありがとうございました。 改めて基本的な部分から見直すきっかけとなり、大変有意義なQ&Aとなりました。 ベストアンサーは決めにくいので、とりあえず本回答をベストアンサーとして、質問を締め切らせて頂きます。 ご回答頂いた全ての皆様、ありがとうございました。