独習C++メモ : 第3章


## オブジェクトの代入
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` キーワード付きで宣言することによって、その関数からクラスの非公開メンバへのアクセスが可能になる。  
フレンド関数はクラスのメンバではないことに注意。  
また、フレンド関数は継承で引き継ぐことはできないため、フレンド関数をもつクラスの派生クラスでは有効にはならない。