C++編 目次

 コンストラクタ
  /デストラクタ

 


・メンバイニシャライザでの初期化順序

・オブジェクトを作った後に初期化する
・明示的なブロック化

 演算子

 

・代入演算子のオーバーロード

 文字列操作

 

・stringの多重連結を使用した代入

 

 


トップページへ戻る

代入演算子のオーバーロード

 まず、次のようなクラスを考えてみます。これはファイルアクセスのラッパークラスで、ファイルデスクリプタをメンバに持ちます。

 このクラスの特長は、デストラクタでクローズ処理をするので、使う側は明示的にクローズ処理をする必要がありません。また、コンストラクタの引数でファイルデスクリプタをとることができます。これは、すでにオープン済みのファイルを使用してクラスオブジェクトを作ることができるようにしたものです。

class CFileWrap
{
public:
    CFileWrap() : m_fd(-1) {}
    CFileWrap(int fd) : m_fd(fd) {} // デスクリプタ指定の初期化
    virtual ~CFileWrap() {Close();}
    
    // ファイルオープン
    int Open(string path)
    {
        Close();
        return (((m_fd = open(path.c_str(), O_RDWR)) == -1) ? 1 : 0);
    }
    
    // ファイルクローズ
    void Close() {if (m_fd != -1) {close(m_fd), m_fd = -1;}}
    
    // ファイル読み込み
    int Read(void *buf, int len)
    {
        int err = 0;
        
        if (!buf) err = 1;
        if (!err) if (m_fd == -1) err = 1;
        if (!err) if (read(m_fd, buf, len) == -1) err = 1;
        return err;
    }
    
private:
    int     m_fd;    // デスクリプタ
};

 では、このクラスの使用例を見てみます。

#include "CFileWrap.h"

int main(int argc, char *argv[])
{
    CFileWrap   file1, file2, file3;
    int         fd = -1;
    int         err = 0;
    
    err = file1.Open("test1.txt");  // (1)通常のオープン
    if (!err)
    {
        file2 = file1;      // (2)他オブジェクトからの代入
    }
    if (!err)
    {
        err = ((fd = open("test2.txt", O_RDWR)) == -1) ? 1 : 0;
    }
    if (!err)
    {
        CFileWrap   file4(fd);  // (3)デスクリプタ指定の初期化
    }
    if (!err)
    {
        err = ((fd = open("test3.txt", O_RDWR)) == -1) ? 1 : 0;
    }
    if (!err)
    {
        file3 = CFileWrap(fd);  // (4)一時オブジェクトを使った初期化
    }
    return 0;
}

 (1)は通常の使用方法です。オブジェクトを作った後にOpen()関数を使用してファイルをオープンしています。

 (2)は初期化済みの他のオブジェクトを代入しています。このようにするとメンバがすべてコピーされます。

 (3)はオープン済みのファイルのデスクリプタを指定して初期化しています。

 (4)はオープン済みのファイルのデスクリプタを使って初期化した一時オブジェクトを代入しています。

 ここで、(2)と(4)の場合は問題が起こります。このクラスはデストラクタでファイルをクローズするので、(2)の場合は、コピー元とコピー先で同じファイルに対して2回クローズ処理が実行されてしまいます。また、(4)の場合は代入処理が実行された後に一時オブジェクトのデストラクタが呼ばれるので、コピー先のオブジェクトが保持しているデスクリプタは無効になってしまいます。

 このような問題を回避するためには、代入演算子をオーバーロードします。オーバーロードした代入演算子は次のようになります。dup()関数はファイルデスクリプタを複製します。CFileWrap型のオブジェクトを代入したときに、代入元のデスクリプタを複製して保持するようにしているわけです。こうすると、複製元と複製先の両方のデスクリプタがクローズされたときに、初めてファイルがクローズされるようになります。

class CFileWrap
{
public:

...
    // 代入演算子オーバーロード
    CFileWrap &operator =(const CFileWrap &cFileWrap)
    {
        if (cFileWrap.m_fd != -1) m_fd = dup(cFileWrap.m_fd);   
        return *this;
    }
...

};

 また、代入演算子は自分自身の参照を返すようにします。

 この例ではデストラクタでファイルのクローズをしていますが、クラス内部で動的なメモリを確保して、デストラクタで開放するような仕組みのクラスでも、全く同じ問題が起こります。この場合も、代入演算子をオーバーロードして、動的なメモリとその内容を複製するようにすれば、2重にメモリが開放されるのを防ぐことができます。