Rokiのチラ裏

学生による学習のログ

ジェネリックなRAIIラッパーunique_resource

標準化委員会による文書:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0052r2.pdf

通常RAIIを採用した場合、例外安全性とムーブコンストラクタ/代入演算子、リソース漏れなどに注意を払いながら確実に実装しなければならない。例えば、これはOS XAPIを用いてインタフェースに設定されているIPアドレスを取得するものだ。

#include<cstring>
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/ioctl.h>
#include<unistd.h>
#include<netinet/in.h>
#include<net/if.h>
#include<arpa/inet.h>

struct IP final{
    explicit IP():fd(socket(AF_INET,SOCK_DGRAM,0))
    {
        ifr.ifr_addr.sa_family=AF_INET;
        std::strncpy(ifr.ifr_name,"en0",IFNAMSIZ-1);
    }
    IP(const IP&)=delete;
    IP& operator=(const IP&)=delete;
    IP(IP&& rhs):fd(socket(AF_INET,SOCK_DGRAM,0))
    {
        ifr.ifr_addr.sa_family=rhs.ifr.ifr_addr.sa_family;
        std::strncpy(ifr.ifr_name,"en0",IFNAMSIZ-1);
        close(rhs.fd);
        rhs.fd=0;
    }
    IP& operator=(IP&& rhs)
    {
        if(this==&rhs)return *this;
        ifr.ifr_addr.sa_family=rhs.ifr.ifr_addr.sa_family;
        std::strncpy(ifr.ifr_name,"en0",IFNAMSIZ-1);
        fd=socket(AF_INET,SOCK_DGRAM,0);
        close(rhs.fd);
        rhs.fd=0;
        return *this;
    }
    void print()
    {
        ioctl(fd,SIOCGIFADDR,&ifr);
        std::cout<<inet_ntoa(reinterpret_cast<sockaddr_in *>(&ifr.ifr_addr)->sin_addr)<<std::endl;
    }
    ~IP(){close(fd);}

    int fd=0;
    ifreq ifr;
};

int main()
{
    IP().print();
}

これは面倒であるし、RAIIを採用するにあたって注意を払わなければならない点は固有であるのだから、フレーム化してしまうべきである。そこで、unique_resourceの出番である。このエントリの執筆時点では標準ライブラリにまだ追加されていないので有志によってboostライセンスで公開されているヘッダーを外から持ってくる必要がある。

#include<cstring>
#include<iostream>
#include<my_experimental/unique_resource.hpp> // % git clone https://github.com/okdshin/unique_resource.git

#include<sys/types.h>
#include<sys/socket.h>
#include<sys/ioctl.h>
#include<unistd.h>
#include<netinet/in.h>
#include<net/if.h>
#include<arpa/inet.h>

int main()
{
    auto fd=std_experimental::make_unique_resource(socket(AF_INET,SOCK_DGRAM,0),[](auto fd){close(fd);});
    
    ifreq ifr;
    ifr.ifr_addr.sa_family=AF_INET;
    std::strncpy(ifr.ifr_name,"en0",IFNAMSIZ-1);
    ioctl(fd,SIOCGIFADDR,&ifr);
    
    std::cout<<inet_ntoa(reinterpret_cast<sockaddr_in *>(&ifr.ifr_addr)->sin_addr)<<std::endl;
}

まあ物としてはstd::unique_ptrに備えられているカスタムデリータ機能を使ってポインタ以外での何らかの解放処理が必要なリソースに対して汎用的に管理できるようにしたものである。

余談

このヘッダーはN4189を参照しており、執筆時現在unique_resourceらの提案における最新の提案書(p0052r2)準拠でない。*1
例えば、constructors and specification by only allowing nothrow-copyable RESOURCE and DELETER typesでないリソース、デリータを渡す事は許さないという提案がある。p0052r2から一部引用する。

In Kona LWG gave a lot of feedback and especially expressed the desire to simplify the constructors and specification by only allowing nothrow-copyable RESOURCE and DELETER types. If a reference is required, because they aren’t, users are encouraged to pass a std::ref/std::cref wrapper to the factory function instead.
  • Simplified constructor specifications by restricting on nothrow copyable types. Facility is intended for simple types anyway. It also avoids the problem of using a type-erased std::function object as the deleter, because it could throw on copy.

これには例えばstd::functionが該当する。

auto fd=std_experimental::make_unique_resource(socket(AF_INET,SOCK_DGRAM,0),std::function<void(int)>([](int fd){close(fd);})); // 無例外コピー可能な型でなければならない

将来的にはTMPかSFINAEか手法は何であれ、無例外コピー可能な型でない限りエラーとするようになるかもしれない。

参照

*1:この差分らを日本語で追っているQiitaのページがある。一応参照:http://qiita.com/yumetodo/items/3513677a4b894276388