こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

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

下記のようなクラス定義があるとします。
説明のため、関係のない要素は省略し簡略化しています。

class HOGE
{
int a;
char c;
LPSTR lpszStr;

// メンバ関数定義

}

class HOGE_Derived : public HOGE
{
LPSTR _lpszStr;

HOGE_Derived& operator= (HOGE& obj_HOGE);
HOGE_Derived& operator= (HOGE_Derived& obj_HOGE_Derived);

// メンバ関数定義
}


このとき、下記のようなコードで演算子のオーバーロードを行っていますが、これは正しいのでしょうか?


HOGE_Derived& HOGE_Derived::operator= (HOGE& obj_HOGE)
{

memcpy((HOGE*)this, &obj_HOGE_Derived, sizeof(HOGE));

lpszStr = NULL;

// lpszStrのディープコピー
_lpszStr = new char[strlen(obj_HOGE.lpszStr) + 1];
strcpy(_lpszStr, obj_HOGE.lpszStr);
lpszStr = _lpszStr;

return *this;
}

HOGE_Derived& HOGE_Derived::operator= (HOGE_Derived& obj_HOGE_Derived)
{

memcpy((HOGE*)this, (HOGE*)&obj_HOGE_Derived, sizeof(HOGE));

lpszStr = NULL;

// lpszStrのディープコピー
_lpszStr = new char[strlen(obj_HOGE_Derived.lpszStr) + 1];
strcpy(_lpszStr, obj_HOGE_Derived.lpszStr);
lpszStr = _lpszStr;

return *this;
}

期待した動作は、まず最初のmemcpyで、obj_HOGEまたはobj_HOGE_Derivedオブジェクトのうち、基本クラス(HOGE)に存在するフィールドint aとchar cのみをコピーしたいのです。


memcpyによってこのようなコピーを行った時、処理系に依存せず期待した通りコピーできるものなのでしょうか?
そもそも、このようなコピーの仕方自体、自然なやり方でしょうか?

気になるのは、HOGE_Derivedクラスのオブジェクトのメモリ内のデータの並びがどのようになっているのかと言うことです。
HOGE_Derivedクラスのポインタを、その基本クラスであるHOGE型にキャストした時に、このポインタで見えるエリアが、HOGE_Derivedクラスの中のHOGEクラスの部分だけと言うことは分かっているのですが、その部分のメモリ上の並びが必ず基本クラスの各フィールド→派生クラスで定義されたフィールドの順になっているのかどうか分かりません。


よろしくお願いします。

投稿日時 - 2012-03-03 15:15:26

QNo.7339927

困ってます

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

C++(Cの範囲まで含めると)は相当に複雑で
また自由度が高い分プログラマの理解と注意力を要求する言語です。

従って
言語仕様とプログラミングの手法、設計全て込みになってくると、もはや
「知らない、分からない事がある方が遥かに普通」と思った方が良いですね。
考えようによってはプログラミングも芸術の一種です。

基本的なことほど、「調べないので逆に案外気づかない」とかいう事もあります。

世の中のC++のプラグラマー全体の内、今回の質問・回答で出てきたこと全ての内容について、なにも参照し直さず
すらすら答えられる人など、1%もいないんじゃないかという気がします。

なので、もし今後分からない事が出てきたりしたら、どんどん聞いたり調べたりしてみてください。
そうした方が後で自分が助かります。


まぁ、今回の内容に関してはおそらくほとんど網羅できたんではないかと思いますし、よほどの事がない限り私はここいらで「回答サイド」は一時失礼する、かもしれませんw

何しろ、そろそろ自分のプログラムの方に集中すべき時間になってきたっぽいのでw
数年がかりの物ですが、まさにそろそろクライマックスか!?って感じです。

そんではノシ

投稿日時 - 2012-03-05 02:55:26

お礼

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

ご回答頂いた全ての皆様、ありがとうございました。

投稿日時 - 2012-03-05 20:55:01

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(31)

ANo.30

>コンパイル時にクラス型のサイズも確定していて、それが固定値として実行モジュールに書き込まれてしまうと言うことですね?


そう言う事です。
なので同じコードだとしても、別の環境で全く同じコードのままコンパイルしたのを使うと、死ぬ可能性があります。

そこはしっかり確認しておく必要があります。

投稿日時 - 2012-03-04 23:07:33

お礼

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

投稿日時 - 2012-03-04 23:58:47

ANo.29

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

投稿日時 - 2012-03-04 23:04:24

ANo.28

>ここまで来ると、Baseはテンプレートクラスにして「A」と言う型を意識しないような作りにしておく方が良いような気もします。(テンプレートというかインタフェースクラス?)

「再コンパイルの手間」ってことを考えると、テンプレートは結構かかりかねないですよ?w

仮に下のコードの構造をBaseクラスの実装を「ヘッダに書かず」「ソースに書く」としとけば
最強にラッキーな仕様変更や実装方法(GetAとかやらずにgetterを個別に書いたうえで)の場合
再コンパイルすべきソースは「たったの一つ」で済む可能性すらあります。

まぁ、GetA()->メンバとかやってたとしても
そのソースとBaseクラスのソースだけですみます。

でもテンプレートだと、Baseクラスのヘッダを書き変えることになるので
それをインクルードしてるソースは全部再コンパイルの必要が生じます。


「まぁそんな頻繁に目的の構造体は仕様変更されるわけじゃないだろうから良い」なら良いですが


また
Aに当たる部分をテンプレート化する場合は、SetAやFreeABuf関数など部分を、対応できるようにしとかないといけません。

「テンプレートや関数オブジェクトや関数ポインタの仕様には強い」という自身があれば構いませんが、かなりの技量だとしても結構注意力を要することは確かですね。


これらを踏まえてなお
それでいいという明確な理由があるなら、そうしても問題ないと思います。

投稿日時 - 2012-03-04 22:58:22

ANo.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文とかでも事足りるかもしれません。

どっちがより良いかは状況によります。

投稿日時 - 2012-03-04 22:33:41

お礼

再ビルドは少なくとも必要でした。言葉が間違っていました。
言いたかったのは、自作のクラスに手を入れる必要がないと言うことです。これはこれで大きいのです。再ビルドだけなら誰でも出来ますが、コードの修正は分かっている人にしか出来ませんから。

コード例かなりシンプルに分かり易く書いて頂いてありがとうございます。ほぼイメージ通りです。
ここまで来ると、Baseはテンプレートクラスにして「A」と言う型を意識しないような作りにしておく方が良いような気もします。(テンプレートというかインタフェースクラス?)

投稿日時 - 2012-03-04 22:47:07

ANo.26

>その通りです。

「memcpyにしておけば~再ビルドの必要はない」と本当に思われてるのでしたら
それは気のせいです。

>memcpy((HOGE*)this, &obj_HOGE_Derived, sizeof(HOGE));

という記述をしている限り再ビルドは必要になります。
sizeofはコンパイル時点のサイズを求める演算子ということを理解されてますか?
またクラスにしても基底クラスが変更になったのであれば、
その派生クラスの再ビルドは必要です。

memcpyを使用することで再ビルド、テスト、出荷作業をなくしたいようですが
それはなくしたらいけません(なくせません)。面倒がらずにしてください。

投稿日時 - 2012-03-04 22:01:05

お礼

>sizeofはコンパイル時点のサイズを求める演算子ということを理解されてますか?

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

投稿日時 - 2012-03-04 23:01:09

ANo.25

>外部にデータを受け渡す時に、別途メモリを確保してそこにコピーし、渡した先はデータを受け取った後、そのエリアをfreeします。外部とのインタフェースに関しては、そのような仕様になっているので、勝手に変更は出来ません。


なるほど、そういうことですか。
もし「派生クラスにメンバ変数を追加しなくていい」
状況であるならば

この状況下では、やはり基本的な処理に関しては
そのPOD構造体を扱うための、ラッパーとしての、基底クラス(抽象クラス)に包含で含んでおいて
もうちょっと具体的な処理は派生クラスで切り分ける、といった形にしておき

包含しているPODのコピーに関しては
一括=やmemcpyなどがベストな感じに思います。



リファクタリングした方が良さそうな状況だと判明した場合は
既に大量のコードを書いてらっしゃるのでしたら
大変かもしれません。

そこはもう「頑張ってください」、と言うしかありませんが

何年か先になった時に、その見返りは結構なものになる可能性は十分あると思いますから

ここでまだ公開されていない現在の状況含め、じっくりとご検討してみてください。

投稿日時 - 2012-03-04 21:03:41

ANo.24

>「基本クラスに変更が入ってもmemcpyにしておけば、
>ポインタの追加でもないかぎりmemcpyの部分のコードに修正しないので
>再ビルドの必要がなくなり、
>結果それに伴うテストや出荷などの事務手続きも発生しない」って
>ことになりそうなのですが、あってます?


包含であれば、その部分(POD)に対してmemcpyや一括で=でOKですが
継承だと=等で毎回メンバ毎に書くことに

で、私(や先人)のお勧めとしては「継承は最小限のが良いんでない?」(そうすりゃ再コンパイルの手間も最小限だし)
という流れだと思いますよw

投稿日時 - 2012-03-04 20:29:12

お礼

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

投稿日時 - 2012-03-04 20:51:14

ANo.23

>外部から受け取ったデータをクラスで保持する必要があるのです。

なるほど、そのデータはその後「外部から破壊される、あるいは外部から書き換えされる」ものですか?

そうであればコピーは確かに必要かもしれません。


でも継承とかの構造に関しては……
「結合を最小にせよ」というのは、実は私だけが自分の経験からだけで言ってるんじゃなくて

一般的な、C++にディープに触れてる本には、結構書いてあるようなことなんじゃないでしょうか


例えば、今私の手元にある
More Exceptional C++(ハーブ・サッター 著 浜田 真理 訳 浜田 光之 監修)
とかにもふつーに書いてあります。

p157,p158から少し引用させていただきますと

-------------

以前にも主張したように、経験を積んだ開発者でさえ、継承を使いすぎる傾向がある。ソフトウェアエンジニアリングの健全なるルールは、「結合を最小にせよ」だ。複数の方法でクラス間の関係を表現できる場合、最も弱く、かつ実用上十分な関係を採用しよう。継承は、C++のクラス間の関係としてはほとんど最強であり、これより強い関係はfriendしかない
(以下略)

弱い結合は、(例外安全も含め)プログラムの正しさを向上させる。そして、強い結合は、(例外安全も含め)プログラムの正しさの到達可能レベルを下げる。

-------------


こんな感じですよね。
作ってる最中はいいかもしれませんが、ずっと後で見た時や、別の人が見た時

継承使いまくりだと
派生クラスのメンバは、全体として何があるのか
って把握するのも苦労しますし

基底クラスに変更があると、全部コンパイルしなおしになりますし…

包含にしとけば、「例外安全にもしやすい」ですから
エラーとかに強い構造、あとで機能拡張、バグフィックスがしやすいようにしっかり組み上げようとすると

結局、一部で「最初は」多少長く感じても
総合的に見たときに包含の方がシンプルになりやすい、という感覚です。

もちろん、「意味的に継承が最適」なのであればこの限りではありませんが。

投稿日時 - 2012-03-04 20:24:43

お礼

外部にデータを受け渡す時に、別途メモリを確保してそこにコピーし、渡した先はデータを受け取った後、そのエリアをfreeします。外部とのインタフェースに関しては、そのような仕様になっているので、勝手に変更は出来ません。

継承より包含、に関してはもっと専門書等を当たって調べたいと思います。
ただ、今回の場合は、あくまで内部処理用のユーティリティ的な機能を追加したかっただけの継承なので、包含でも確かに問題はないと思います。

アドバイスありがとうございました。

投稿日時 - 2012-03-04 20:49:40

ANo.22

>はい、作業自体はそれほど時間のかかるものではありませんが、製品ですのでテストや出荷等の事務手続きも伴うためなるべくそういったことは発生しないようにしたいと言うことです。

今までの流れからすると、これって
「基本クラスに変更が入ってもmemcpyにしておけば、
ポインタの追加でもないかぎりmemcpyの部分のコードに修正しないので
再ビルドの必要がなくなり、
結果それに伴うテストや出荷などの事務手続きも発生しない」って
ことになりそうなのですが、あってます?

投稿日時 - 2012-03-04 20:21:50

お礼

その通りです。

投稿日時 - 2012-03-04 20:39:48

ANo.21

>こうなっていた場合、aにアクセスするためには、包含を使用する場合、_Bobj.pB->objA.pA->aのような書き方になると思います。
setter/getterを間に挟めば、ますます冗長なコードになります。


それは「概念的に、絶対継承しなくちゃいけない」類の物なのでしょうか?

もし「継承する『必然性』」がないのであれば、冗長でもsetter/getterを挟むほうが「後々遥かに楽に」なりますし、直交した概念のものであれば縦のつながりは少なく、横方向に広げられるはずです。

「多重継承」もそうですが「多段な継承」もまた、しすぎるのはお勧めできません。

あるいは「別途データ受け渡し用の構造体」を作って、Getは一本化してしまうという手もあります。

というより、本当にそんなに「継承する必要のあるクラス」
であるなら、インスタンスのコピーをとらないことに集中するのが本筋です。

コピーがぜーーーーーーったいにいるとしたら
それは
「全く同じデータ群を持つ巨大なものが2つ以上存在する瞬間がないとぜーーーーーーったいに困る」
というケースのみです。


本当に2つ以上存在する瞬間がないとぜーーーーーーったいに困る状況ですか?

投稿日時 - 2012-03-04 19:54:34

お礼

外部から受け取ったデータをクラスで保持する必要があるのです。これを、内部的に扱いやすいように派生させた型のインスタンスとして保持します。つまり、コピー(代入)が発生します。しかも、基本クラス型→派生クラス型への代入(コピーコンストラクタ)となります。
またその逆もあります。この場合は、派生クラス型→基本クラス型へのコピーとなります。


受け渡しするデータ型に値型のデータフィールドしかなければ、ただ単純に=でコピーできますが、どうしても動的にメモリ確保してデータコピーしてやらなければならない場面(ポインタ型フィールド)があるのです。

投稿日時 - 2012-03-04 20:10:35

ANo.20

>しかし、その包含したインスタンスでは、基本クラス(実際は構造体ですが)のフィールドにアクセスするためににいちいち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のメンバにポインタがあって、そのディープコピーも行うべきであればもう一行二行程度書く必要はありますが、「それだけである」とはいえます)

投稿日時 - 2012-03-04 19:17:07

ANo.19

失礼、「コメント部分」がまだミスってました

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);

投稿日時 - 2012-03-04 15:30:12

ANo.18

コピペミスです
下のほう

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;
}

投稿日時 - 2012-03-04 15:14:34

ANo.17

う~ん
私の「継承ではなく包含することについての提案」に対してノータッチですが
理解されてますか?


>メンバの並びについての話がまさに質問したかった事なんですが、やはり処理系に依存する可能性もあると言うことで、このようなケースで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のヘッダ」をインクルードしたソースは無関係です。

投稿日時 - 2012-03-04 15:11:00

補足

さらに補足しますと、最初にサンプルで挙げたクラスは、中に他のクラス(構造体)型のフィールドが含まれていませんが、実際は他のクラス型(もちろん自作型です)のフィールドが複数含まれています。そして、そのクラスも元の構造体から派生したクラス型を使いたいのです。

包含の考え方ですと、包含したクラス自体がまたさらに別のクラスを包含した型となっており、継承の考え方を使わなければ、入れ子になったクラスのフィールドにアクセスするのにいくつものオブジェクトを経由するような形になり、見た目的にも良くないし、ぱっと見で人間の頭で素直に理解しにくいコードになってしまうような気がします。


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を間に挟めば、ますます冗長なコードになります。

投稿日時 - 2012-03-04 19:18:17

お礼

包含ですか。確かにそのようなデータ構造も考えはしました。

しかし、その包含したインスタンスでは、基本クラス(実際は構造体ですが)のフィールドにアクセスするためににいちいちgetter/setterを用意するか、aをpublicにして、b.a->dのようにアクセスしなければならないのです。

まあそうすれば良いだけのことですが、何となくスマートではないなと思ったのと、いわゆるオブジェクト指向的なやり方ではないため、継承を使った構造にしたわけです。

投稿日時 - 2012-03-04 19:01:42

ANo.16

>フィールド一つの変更でも全てビルドし直すのは、結構な手間とコストになるのです。

「修正するのに手間とコストがかかる」ではなく
「ビルドし直すの手間とコストがかかる」なのですか?

Makefileなどをちゃんと書いているなら
makeを起動しコンパイルが終わるのを待つだけで
手間もコストもないと思うのですが・・・
memcpyでもビルドし直さないといけないのは同じだし。

投稿日時 - 2012-03-04 12:51:12

お礼

はい、作業自体はそれほど時間のかかるものではありませんが、製品ですのでテストや出荷等の事務手続きも伴うためなるべくそういったことは発生しないようにしたいと言うことです。

投稿日時 - 2012-03-04 18:36:31

ANo.15

まぁ、一つのPODの構造体から、同じ型の一つの構造体
での話なら

変数名 = 変数名;

でも最適化で同じコードになるかもしれませんし、違うコードが生成されたとしても、計測上の誤差のが勝る事のが遥かに多いんじゃないでしょうか。

memmoveやmemcpyはどっちかっていうと「配列とループ」のかわりに使われることの方が割合はずっと多いんじゃないでしょうか。


「VC++7.1では,PODのポインタに対するcopyはmemmoveで実装されています.」


とかCryoliteさんの日記で見た気がしますが
STL使うと
その辺意識しなくても良いって言う意味はあると思います。

自分の手では「全く使うコードを書かない」でも、プログラムが書けないわけじゃないです。

(まぁ、あくまで「もしコピーをやんなくて済む手がある状況だったら、それがいっちばん速い」ですw
memmoveやmemcpyは最適化が許されてますし、それは相当なものですが
「何もやらない」には確実に勝てませんw)

投稿日時 - 2012-03-04 01:10:53

ANo.14

>「構造体のサイズ」だけ意識してmemcpyで構造体全体をコピーするようにしておけば、構造体の定義が変わっても修正は不要となります。

だから、それを「省略することに異常にこだわっている」といってるんですけどね。
構造体に追加されるのがポインタ変数だと修正不要ともいえないでしょうし。

正直、私はmemcpy、Cでも文字列やバイナリデータのコピーくらいにしか使わないけど。

投稿日時 - 2012-03-04 01:03:41

お礼

本ケースで確実な方法は、単純にメンバ個別に=で代入することだと分かりましたので、memcpyは使用しないようにしたいと思います。色々とありがとうございました。

ただ補足するなら、基本クラス(構造体ですが)は、5年程度のスパンで見れば割と仕様変更は発生します。またそのような構造体が何十とあります。フィールド一つの変更でも全てビルドし直すのは、結構な手間とコストになるのです。もちろんポインタ変数が追加されればディープコピー処理を追加しなければならないですが。

投稿日時 - 2012-03-04 09:27:09

ANo.13

ぶっちゃけた話, C でもそれほど memcpy が必要とは思えない. 単に代入で書いておいて, コンパイラにまかせた方がいいと思うけどねぇ. 配列データのコピーだって std::copy を使う手もある.

あれ? よく考えてみたら
lpszStr = NULL;
ってアウトじゃない? このメンバは HOGE から継承してるんだけど, HOGE で (デフォルトの) private とされているのでアクセスできないはず....

あと, 最後のメンバの並びについては「それぞれのクラスでかたまっている」ように配置しないと困りますよね. 配置する順序は決まっていないと思ういます. つまり, コンパイラによっては「基本クラス→派生クラス」かもしれないしその逆かもしれない.

投稿日時 - 2012-03-04 00:54:53

補足

>ぶっちゃけた話, C でもそれほど memcpy が必要とは思えない. 単に代入で書いておいて, コンパイラにまかせた方がいいと思うけどねぇ

単なる値型のデータならそれで良いし、こんなに悩むことはないと思います。
ただ単純に=で代入すれば良いだけの話しです。
コンパイラがデフォルトの=演算子を定義してくれて、各メンバが全てコピーされるわけですね。しかし、ポインタが含まれているとそうはいかない。そのため=演算子をオーバーロードするわけですが、この中で第二オペランドを=で第一オペランドに代入することは出来ません。(無限ループになる)
なので、内部を直接コピーしてやる必要があるのですが、その手段が正しいかどうかの質問でした。

投稿日時 - 2012-03-04 09:21:14

お礼

ご回答ありがとうございます。

元のクラスHOGEは実際は構造体でした。説明のために多少改変しましたが、lpszStrのディープコピーの部分は質問の本質ではありませんので、適当に書いてしまいましたm(_)m


メンバの並びについての話がまさに質問したかった事なんですが、やはり処理系に依存する可能性もあると言うことで、このようなケースでmemcpyは使用しないようにしようと思います。ただ純粋にPOD型の構造体同士のコピーなら、つまりC言語と互換なのでmemcpyが使えると思います。

投稿日時 - 2012-03-04 09:10:50

ANo.12

>>それ以外のオブジェクト型をmemcpyでコピーするには常に危険がつきまとうと言う感じでしょうか。

>はい、そう理解しておくのが確実ですね。

おっと、一応
今までのやりとりから、書く必要はないとは思いますが、PODでありさえすればmemcpyやmemmoveなどOKです。

投稿日時 - 2012-03-04 00:53:22

ANo.11

>ただ、基本クラスは外部が設計したクラスで、これを変更することは出来ません。

な~るほど。

>なので、それを内部的に使い勝手の良いように派生クラスを作成したと言うわけです。
派生クラス同士、及び基本クラス⇔派生クラスのコピーは必要になる場面があるのです。


ホントにダメですか?(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でコピーするには常に危険がつきまとうと言う感じでしょうか。

はい、そう理解しておくのが確実ですね。

投稿日時 - 2012-03-04 00:45:21

ANo.10

>しかし、そうするとC++でmemcpy関数の出番はほとんどないのではないでしょうか?

ある型がPODでなくても「ある型のポインタ」はPODですから

ポインタの配列だったら移せますし(これが超便利なこともありますし、これだけでもかなりの事が出来ます)
そうでなくとも巨大な配列をコピーする必要がある場合だと重宝する可能性があります。

というより、「C++11でわざわざPODの定義の見直しがされた」
ことからもわかるように、パフォーマンスが要求される場面や
標準的な機能を提供するライブラリ(や、それを自作する場合)などでは
十分使える機能です。

もちろん、「コピーその物を最小限」である方が良いプログラムですが、これはその上でさらにもう一歩踏み込む場合に使う、ということです。

投稿日時 - 2012-03-04 00:18:58

お礼

なるほど、そういうことですね。
いずれにしても、単純な値型(ポインタ型も含む)を大量にコピーする際には使えるが、それ以外のオブジェクト型をmemcpyでコピーするには常に危険がつきまとうと言う感じでしょうか。

投稿日時 - 2012-03-04 00:27:04

ANo.9

>しかし、そうするとC++でmemcpy関数の出番はほとんどないのではないでしょうか?

はい、出番はほとんどないと思います。
Cとの互換性のために残ってるようなものと思ってよいかと思います。

投稿日時 - 2012-03-04 00:11:36

ANo.8

>もう一つ、何度も質問して申し訳ありませんが、最初の質問のサンプルコードで、基本クラス(HOGE)が、クラスではなくPOD型の構造体であったとします。(プリミティブ型の他、ポインタ型も含みます)
(以下略)

POD型であるなら問題ないと思います。
私ならmemcpyより可能な限り=使いますけどね。

ただまぁ・・・スマートというより、コードの記述量を減らす(省略する)ことに異常にこだわってるだけのように思えるんですけど。
私としてはスマートな記述というのは省略することではなくて
無駄なコードがないことだと思いますが。

投稿日時 - 2012-03-04 00:05:06

お礼

なるほど、確かに=でコピーした方が確実は確実でしょうね。

ただし、メリットはコードの記述量を減らすだけではありません。
メンバ一つ一つを意識したコピーをすれば、メンバが追加になった時はコードの修正が必要になります。
「構造体のサイズ」だけ意識してmemcpyで構造体全体をコピーするようにしておけば、構造体の定義が変わっても修正は不要となります。

投稿日時 - 2012-03-04 00:24:40

ANo.7

う~ん

>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などはあきらめて、ポリモーフィズムやアクセス修飾子の強化といった、継承の旨みを十分に生かすというアプローチで「全体のコードをスマートにする」方が無難だし、確実だし、言語仕様に対して素直な方法に思います。

投稿日時 - 2012-03-03 23:57:03

お礼

おっしゃるとおりですね。

ただ、基本クラスは外部が設計したクラスで、これを変更することは出来ません。
なので、それを内部的に使い勝手の良いように派生クラスを作成したと言うわけです。
派生クラス同士、及び基本クラス⇔派生クラスのコピーは必要になる場面があるのです。

投稿日時 - 2012-03-04 00:37:01

ANo.6

POD かどうかによらず, 自信をもって大丈夫といえる状況でなければ memcpy は避けるべきじゃないかなぁ.

少なくとも, 親クラスに operator = を実装し, 子クラスからはそれを呼び出すようにすれば責任は回避できるよね.

投稿日時 - 2012-03-03 23:32:22

補足

しかし、そうするとC++でmemcpy関数の出番はほとんどないのではないでしょうか?
プリミティブ型であれば普通に代入で済みますし、文字列はstrcpyでコピーできます。
プリミティブ型の配列のコピーくらいでしょうか。

投稿日時 - 2012-03-03 23:56:41

ANo.5

>代入演算子をオーバーロードするよりは、Copy関数のようなものを作って、その中で(コンパイラがデフォルトで作成する代入演算子関数によって)通常の代入を行った上で、ポインタ部分のディープコピーを行う形にした方が良いのでしょうか?

私がもしやる場合ですが、
classとして定義するなら代入演算子をオーバーロードして
面倒でもメンバー一つづつコピーします。

structで定義するならPOD型になるようにした上でメンバは
利用する側からはわからないように必ずポインタで操作するようにし、
そのためのコンストラクタ、デストラクタ、代入演算子に対応する関数を用意します。

>クラスではなく構造体なら上記のようなmemcpyによる代入は問題ないのでしょうか?
>C++の場合、クラスも構造体もほぼ同じだと思うのですが。
>違いはデフォルトのアクセス修飾子くらい?

POD型を調べれば、おのずとわかりますよ。
私が「POD型」という言葉を知ったのも、この質問に回答してなので
調べるのに30分もいらないと思います。

投稿日時 - 2012-03-03 22:15:37

お礼

POD型調べてきました。
結局メンバ関数が含まれていればPOD型ではないと言うことなのですね。
この場合は構造体でもmemcpyは出来ないと。

もう一つ、何度も質問して申し訳ありませんが、最初の質問のサンプルコードで、基本クラス(HOGE)が、クラスではなくPOD型の構造体であったとします。(プリミティブ型の他、ポインタ型も含みます)
派生クラスのインスタンスを基本クラスのインスタンスにキャストした上で、sizeof(HOGE)バイト分ををmemcpyする分には問題ないのでしょうか?
その上で、派生クラスで追加定義したフィールドを個別に手動でコピーすれば、比較的スマートになると思いました。(やはりメモリ上の物理的な並びが問題になる?)

そもそも上記サンプルは説明のために多少改変しましたが、実際にはHOGEはPOD型の構造体なのです。この型を使いやすいようにするために、内部処理用として派生クラスを作成してメンバ関数を追加したのでした。ところが、外部とのやりとりは基本クラス型のインスタンスで行う必要があるため、型変換用にoperator=のオーバーロードが必要なのでした。

投稿日時 - 2012-03-03 22:56:21

ANo.4

>インスタンスコピーにmemcpyを使ってはいけない理由は何でしょうか?
>クラスに仮想関数は使用していないため、仮想関数ポインタ等の隠しフィールド(?)が挿入されていることもないと思います。

仮想関数がないからといってCの構造体と互換性があるとはいえないからです。
「POD型」を調べてみてください。
そしてその「POD型」の定義もC++03とC++11では異なります。

>フィールド数が何百もある場合、その一つずつをコピーするコードを書くのは大変だと思うのですが。

そのクラス設計自体がおかしい。

投稿日時 - 2012-03-03 20:30:53

補足

もう一つお聞きして良いでしょうか?
クラスではなく構造体なら上記のようなmemcpyによる代入は問題ないのでしょうか?
C++の場合、クラスも構造体もほぼ同じだと思うのですが。
違いはデフォルトのアクセス修飾子くらい?

投稿日時 - 2012-03-03 21:48:23

お礼

POD型、調べてみます。

やはりクラスのコピーにmemcpyは使わない方が良いと言うことですね。

=演算子をオーバーロードして、ポインタを含むクラスのコピーをスマートに行おうとしたのですが。

代入演算子をオーバーロードするよりは、Copy関数のようなものを作って、その中で(コンパイラがデフォルトで作成する代入演算子関数によって)通常の代入を行った上で、ポインタ部分のディープコピーを行う形にした方が良いのでしょうか?

この場合は、=演算子は使えない(使うといわゆるシャローコピーになってしまう)ため、使わないようにしなければなりませんが。

ちなみにここに似たような質問がありました。
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1339047348

投稿日時 - 2012-03-03 21:35:49

ANo.3

一番の違いは「コンストラクタが呼ばれない」でしょう。

> 気になるのは、HOGE_Derivedクラスのオブジェクトのメモリ内のデータの並びがどのようになっているのかと言うことです。

そういうのが気になるのなら、なおさらmemcpy使わない方がいいでしょう。
どう配置するか、最終的には処理系に依存することですから。


> フィールド数が何百もある場合

これは、また別の問題。そんな無茶な設計自体を見直すのが先です。

投稿日時 - 2012-03-03 20:11:46

お礼

ご回答ありがとうございます。

コード例には省略しましたが、operator=演算子のオーバーロードの中でデストラクタを呼んでいます。内部を一旦クリアしてから、引数(第2オペランド)のデータで初期化しているわけです。

>そういうのが気になるのなら、なおさらmemcpy使わない方がいいでしょう。
どう配置するか、最終的には処理系に依存することですから。

やはりそうですか。要素一つずつコピーしてやるのが確実と言うことですね。

フィールド数に関しては、何百というのは極端でしたm(_)m
でも実際10以上はあるので、一つずつ=で代入するのもスマートではないと思いました。

投稿日時 - 2012-03-03 20:55:44

ANo.2

>インスタンスをmemcpyしているのではなく、インスタンスをコピーする演算子のオーバーロード関数でmemcpyした後、ポインタ部分をディープコピーしようとしています。

代入演算子のオーバーロード関数内だろうが、そうでなかろうが
インスタンスをmemcpyしてるのには変わりないです。

>こういう場合は、memcpyではなく、各フィールドを一つずつ代入してやらなければならないのでしょうか?

そういうことです。
memcpyしてよいのはプリミティブ型(char,intなど)だけと思ってください。

投稿日時 - 2012-03-03 18:17:45

補足

念のため補足しますが、ここで定義しているクラスは完全自作のクラスです。また、中身を知らないオブジェクト型のフィールドはありません。プリミティブ型、ポインタ型、及び自作の他のクラス型のみです。もちろんそのクラスの中身も上記と同様です。

ポインタ型のフィールドに関しては、既に説明済みの通り、一旦memcpyにより値がコピーされますが、直後に参照先をコピーした上でそちらを参照するように書き換えています。

投稿日時 - 2012-03-03 19:01:31

お礼

インスタンスコピーにmemcpyを使ってはいけない理由は何でしょうか?
クラスに仮想関数は使用していないため、仮想関数ポインタ等の隠しフィールド(?)が挿入されていることもないと思います。

外部で直接memcpyを行わないように、=演算子をオーバーロードして、内部的にmemcpyした後、間接参照部分(ポインタ)はディープコピーし、参照先を書き換えています。これでは安全ではないと言うことでしょうか?
フィールド数が何百もある場合、その一つずつをコピーするコードを書くのは大変だと思うのですが。

投稿日時 - 2012-03-03 18:33:26

ANo.1

インスタンスのコピーにmemcpyを使ってはいけません。

投稿日時 - 2012-03-03 17:05:49

お礼

ご回答ありがとうございます。
インスタンスをmemcpyしているのではなく、インスタンスをコピーする演算子のオーバーロード関数でmemcpyした後、ポインタ部分をディープコピーしようとしています。
こういう場合は、memcpyではなく、各フィールドを一つずつ代入してやらなければならないのでしょうか?

投稿日時 - 2012-03-03 17:33:04

あなたにオススメの質問