|
代入演算子のオーバーロード
まず、次のようなクラスを考えてみます。これはファイルアクセスのラッパークラスで、ファイルデスクリプタをメンバに持ちます。
このクラスの特長は、デストラクタでクローズ処理をするので、使う側は明示的にクローズ処理をする必要がありません。また、コンストラクタの引数でファイルデスクリプタをとることができます。これは、すでにオープン済みのファイルを使用してクラスオブジェクトを作ることができるようにしたものです。
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重にメモリが開放されるのを防ぐことができます。
|