ディープコピー
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 15/61。
C言語で、ある構造体を別の構造体に代入すると、シャローコピー(浅いコピー)が行われます。つまり、すべてのフィールドがバイト単位でコピーされます。これは整数のような単純な値の場合は問題ありませんが、ポインタに関しては問題が発生します。両方の構造体が同じメモリを指すことになってしまうからです。
typedef struct {
char *name;
int age;
} Person;
Person alice = {"Alice", 30};
Person copy = alice; // シャローコピー(浅いコピー)!
// alice.name と copy.name の両方が同じ文字列を指しています
一方を解放すると、もう一方は無効になります。一方を通じて文字列を変更すると、両方にその変更が反映されます。これは、ほとんどの場合、意図した動作ではありません。
ディープコピーは、ポインタメンバに対して新しいメモリを割り当て、実際のデータをコピーします:
Person *clone_person(const Person *original) {
Person *copy = malloc(sizeof(Person));
copy->name = malloc(strlen(original->name) + 1);
strcpy(copy->name, original->name);
copy->age = original->age;
return copy;
}
これで、各オブジェクトが独自の文字列を所有するようになります。他方に影響を与えることなく、一方を修正したり解放したりできます。このパターンは単純明快です。各ポインタメンバに対して、単にアドレスをコピーするのではなく、新しいメモリを割り当てて内容をコピーします。
チャレンジ
簡単ディープコピー(深いコピー)パターンを実演する Student モジュールを作成しましょう。元のオブジェクトを変更したり解放したりしてもクローンに影響を与えない、完全に独立した学生オブジェクトのコピーを生成するクローン関数を作成します。
3つのファイルを作成します:
student.h: 2つのメンバを持つStudent構造体を宣言します:char *name(動的に割り当てられた文字列)とint grade。以下の3つの関数を宣言します:create_student— 名前と成績を受け取り、新しい Student へのポインタを返すコンストラクタclone_student—const Student *を受け取り、新しく独立したコピーへのポインタを返す関数free_student— 割り当てられたすべてのメモリを適切に解放するデストラクタ
STUDENT_Hシンボルを使用してインクルードガードを記述してください。student.c: 3つの関数すべてを実装します。clone_student関数が鍵となります。構造体と名前の文字列の両方に新しいメモリを割り当て、ポインタのアドレスだけでなく実際の文字列データをコピーする必要があります。これにより、クローンはオリジナルとは完全に分離された独自のメモリを所有することになります。main.c: 学生を作成し、それをクローンし、両方の学生の情報を表示してから、両方のオブジェクトを適切にクリーンアップします。
2つの入力を受け取ります:学生の名前(文字列)と成績(整数)です。
メインファイルで、提供された値を使用して学生を作成し、その学生のクローンを作成します。オリジナルとクローンの両方の情報を出力して、同じデータが含まれていることを確認します。最後に、両方の学生を解放し、確認のメッセージを出力します。
出力は以下の形式で行ってください:
Original: {name}, Grade: {grade}
Clone: {name}, Grade: {grade}
Both freed例えば、入力が Alice と 95 の場合、出力は以下のようになります:
Original: Alice, Grade: 95
Clone: Alice, Grade: 95
Both freedディープコピーパターンは、各オブジェクトが真に独立していることを保証します。クローンはオリジナルのメモリへの共有ポインタではなく、独自に割り当てられた文字列を保持します。
チートシート
C言語で構造体を別の構造体に代入すると、シャローコピー(浅いコピー)が行われます。すべてのフィールドがバイト単位でコピーされます。ポインタが含まれる場合、両方の構造体は同じメモリを指すことになります:
typedef struct {
char *name;
int age;
} Person;
Person alice = {"Alice", 30};
Person copy = alice; // シャローコピー!
// alice.nameとcopy.nameの両方が同じ文字列を指しています
ディープコピー(深いコピー)は、ポインタメンバに対して新しいメモリを割り当て、実際のデータをコピーします:
Person *clone_person(const Person *original) {
Person *copy = malloc(sizeof(Person));
copy->name = malloc(strlen(original->name) + 1);
strcpy(copy->name, original->name);
copy->age = original->age;
return copy;
}
ディープコピーのパターン:各ポインタメンバに対して、アドレスだけでなく、新しいメモリを割り当てて内容をコピーします。これにより、各オブジェクトが独自のメモリを所有し、独立して変更や解放を行うことができるようになります。
自分で試してみよう
#include <stdio.h>
#include <stdlib.h>
#include "student.h"
int main() {
char name[100];
int grade;
// 入力を読み込む
scanf("%s", name);
scanf("%d", &grade);
// TODO: 提供された name と grade を持つ student を作成する
// TODO: student のクローンを作成する
// TODO: オリジナルの student の情報を表示する
// 形式: "Original: {name}, Grade: {grade}"
// TODO: クローンの情報を表示する
// 形式: "Clone: {name}, Grade: {grade}"
// TODO: 両方の student を解放する
// 確認メッセージを表示する
printf("Both freed\n");
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。