オブジェクトの代入
2つのオブジェクトの型が同じであれば、1つのオブジェクトをもう1つのオブジェクトへ代入できる。
以下の例ではオブジェクトの実体 obj1
を obj2
に代入しているが、このとき obj1
のすべてのデータメンバが obj2
にコピーされるため、obj1
のデータを変更しても obj2
には影響しない。
class MyObject {
int num;
public:
MyObject(int num) { this->num = num; }
void setNum(int num) { this->num = num; }
int getNum() { return this->num; }
};
int main(int argc, char *argv[]) {
MyObject obj1, obj2;
obj1.setNum(10);
obj2 = obj1;
std::cout << "Num1: " << obj1.getNum() << std::endl; // Num1: 10
std::cout << "Num2: " << obj2.getNum() << std::endl; // Num2: 10
return 0;
}
オブジェクトの実体がコピーされた場合、コンストラクタは呼び出されないが、オブジェクトが破棄される時は通常通りデストラクタが呼び出される。
次の例ではエラーが発生する。
class String {
int len;
char *str;
public:
String(char *str);
~String();
void show() { std::cout << this->str << std::endl; }
};
String::String(char *str) {
this->len = strlen(str);
this->str = (char *)mallon(len + 1);
strcpy(this->str, str);
}
String::~String() {
free(this->str);
}
int main(int argc, char *argv[]) {
String s1("hoge"), s2("piyo");
s1.show(); // hoge
s2.show(); // piyo
s2 = s1; // Error!
return 0;
}
String
クラスではコンストラクタでメモリを動的に確保し、デストラクタで解放していることに注意。
s2 = s1;
でオブジェクトがコピーされた時、s1
と s2
のメンバ char *str
は同じポインタを指すようになるので、s1
と s2
が破棄される時にメモリの二重解放が起こるためエラーになる。
関数へのオブジェクトの引き渡し
関数の引数には基本型と同じようにオブジェクトも渡すことができる。
オブジェクトの実体を引数として渡した場合、オブジェクトはコピーされて関数へ渡される。
そのため、関数でオブジェクトのデータを変更しても呼び出し側のオブジェクトは変更されない。
class MyObject {
int num;
public:
MyObject(int num) { this->num = num; }
void setNum(int num) { this->num = num; }
int getNum() { return this->num; }
};
void incrementNum(MyObject object)
{
int num = object.getNum();
object.setNum(num + 1);
}
int main(int argc, char *argv[]) {
MyObject obj(10);
std::cout << "Num: " << obj.getNum() << std::endl; // Num: 10
incrementNum(obj);
std::cout << "Num: " << obj.getNum() << std::endl; // Num: 10
return 0;
}
関数側でオブジェクトのデータを変更したい場合はポインタを渡す必要がある。
void incrementNum(MyObject *object)
{
int num = object->getNum();
object->setNum(num + 1);
}
int main(int argc, char *argv[]) {
MyObject obj(10);
std::cout << "Num: " << obj.getNum() << std::endl; // Num: 10
incrementNum(&obj);
std::cout << "Num: " << obj.getNum() << std::endl; // Num: 11
return 0;
}
関数からのオブジェクト返し
関数からオブジェクトを返すときには、戻り値を格納する一時オブジェクトが作成され、関数からはこの値が返される。
その後一時オブジェクトはスコープを抜け破棄されるので、デストラクタが呼び出されるが、ここで動的に確保したメモリを解放する処理を行っている場合は、やはり二重解放などの予期せぬ動作に注意しなければならない。
フレンド関数
C++ではフレンド関数というものがサポートされており、関数をあるクラスのメンバにすることなくクラスの非公開メンバにアクセスできる機能である。
class MyObject {
int num;
public:
MyObject(int num) { this->num = num; }
friend int getNumFromMyObject(MyObject object);
};
int getNumFromMyObject(MyObject object) {
return object.num;
}
int main(int argc, char *argv[]) {
MyObject object(10);
std::cout << "Num: " << getNumFromMyObject(object) << std::endl; // Num: 10
}
クラス宣言でフレンドとする関数を friend
キーワード付きで宣言することによって、その関数からクラスの非公開メンバへのアクセスが可能になる。
フレンド関数はクラスのメンバではないことに注意。
また、フレンド関数は継承で引き継ぐことはできないため、フレンド関数をもつクラスの派生クラスでは有効にはならない。