前節のC++による単純なserver programは典型的な誤り例であると述べた。 ここではこの問題を考察しよう。 どこが誤りであるかというと、mysocket class で copy constructor と代入演算子が定義 されていない、すなわち default のそれらが使われている点である。 default では各メンバーデータはそのまま代入される。つまり socket descriptor は int なので、int の代入が起こる。 しかし socket descriptor は socket object を int で表現している だけであって数学上の int ではない。四則演算ができるわけではない。 代入もしかり。単純に同じ値にすればよいというものではない。 前節の例では accept() でこの問題が起こっている。accept() は 受け入れた新しい mysocket object を返す。 accept関数の中では、mysocket object である tmpsock を 用意し、このデータメンバーを設定して tmpsock を戻り値にして いる。すなわち tmpsock を呼び出し側 stack へコピーして 関数は終了し、そこから呼び出し側に用意された空の mysocket t へ 代入される。 では tmpsock はどうなるだろう?関数から抜ける時点で scope から 抜けるので destructor が呼ばれる。destructor は socket descriptor を close する。今仮に accept した socket descriptor が 5 だとすると、 5 が closeされる。にもかかわらず 5 は呼び出し側の t の socket descriptor の 値でもある。つまり t という object は以後 close された 5 に対し 操作を行うことになる。これが動作しない理由である。 では、この問題の解決はどうすればよいか? システムにはこのような目的のために descriptor を複写する関数が 用意されている。これを使わなければならない。 具体的には dup関数(Windows の場合は DuplicateHandle?関数)を 使う。 dup関数は、同一実体に対し、異なる descriptor 値を割り当てて くれる。dup関数を使えばシステムは同一実体に対し、 複数の descriptor を与えたことを認識してくれるので、 一方を close しても実体そのものは解放しない。 割り当てたすべての descriptor が close された時点で初めて実体そのものも 解放する。従って、例えば descriptor 5 に対し、dup により 7 が与えられた とすると、5 を close しても 7 は使える。 というわけで、copy constructor と代入演算子を宣言する。 #ifndef MYSOCKET_H_INCLUDED #define MYSOCKET_H_INCLUDED #include <unistd.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> class mysocket { public: mysocket(); mysocket(const mysocket& from); mysocket(int domain, int type, int protocol=0); virtual ~mysocket(); virtual mysocket& operator=(const mysocket& from); virtual int close(); virtual int connect(const struct sockaddr* addr, socklen_t len); virtual int bind(const struct sockaddr* addr, socklen_t len); virtual int listen(int backlog); virtual mysocket accept(struct sockaddr* addr, socklen_t* addrlen); virtual int send(const void* msg, size_t len, int flags=0); virtual int recv(void* msg, size_t len, int flags=0); protected: int m_sd; }; #endif つけ加わった copy constructor と代入演算子は dup を使って mysocket::mysocket(const mysocket& from) { m_sd = -1; if(from.m_sd != -1) m_sd = ::dup(from.m_sd); } mysocket& mysocket::operator=(const mysocket& from) { if(&from == this) return *this; close(); if(from.m_sd != -1) m_sd = ::dup(from.m_sd); return *this; } と書ける。 |