103. 入出力に関するテンプレート(オリジナル)

103.1 安全な gets 関数

バッファオーバーランを検出できないために、とかく嫌われがちな gets 関数ですが、安全性の問題さえなければ便利な関数ですので、できれば使いたいものです。というわけで、安全な gets 関数を作ってみることにしました。

#include <iostream>
#include <cstring>

template<std::size_t N>
char* safe_gets(char (&s)[N])
{
  char t[N];
  std::cin.getline(t, N, '\n');
  if (std::cin.bad())
  {
    return 0;
  }
  return std::strcpy(s, t);
}

この safe_gets 関数は、引数として char の配列型しか指定することができません。mallocnew 等で割付けたときのように、ポインタだけで管理しているような配列を渡すことはできません(コンパイルエラーになります)。

あとは普通の gets 関数と同じですが、C++なので stdin ではなく cin を用いて実装してみました。結果として、エラー発生時の処理の仕方が若干異なります。

safe_gets 関数の内部で I/O エラーが発生した場合、bagbit がセットされます。この場合、safe_gets 関数は空ポインタ(NULL)を返します。なお、ここが重要なポイントですが、I/O エラー発生時には配列の内容は破壊されません。

バッファのサイズが足りなかった場合、入力した文字を可能なところまで配列に格納します。そして、failbit がセットされます。続きを入力したい場合には、必ず cin.clear() を呼び出してください。

103.2 cout にワイド文字を出力するテンプレート

C++では、std::cout をはじめ、std::ostream すなわち std::basic_ostream&ltchar> に直接ワイド文字を出力することができません。無理やり出力しようとしても、単なる整数型とみなされて、文字コードの値が出力されるだけです。

ワイド文字を使わない国の人たち、あるいは標準ライブラリを軽視している人たちはそれでも構わないのかもしれませんが、我々日本人は、やはり std::cout のようなよく使う char ベースのストリームに、直接ワイド文字を出力できると大変便利です。

そこで、char ベースの出力ストリームにワイド文字を直接出力できるテンプレートを作ってみました。

template<class Traits>
std::basic_ostream<char, Traits>&
  operator<<(std::basic_ostream<char, Traits>& ostr, wchar_t wc)
{
  typename std::basic_ostream<char, Traits>::sentry se(ostr);
  if (se)
  {
    typedef std::codecvt<wchar_t, char, std::mbstate_t> codecvt_t;
    const codecvt_t& cvt = std::use_facet<codecvt_t>(ostr.getloc());
    std::mbstate_t st;
    char t[MB_LEN_MAX];
    const wchar_t* fn;
    char* tn;

    if (cvt.out(st, &wc, &wc + 1, fn, t, t + MB_LEN_MAX, tn)
      == std::codecvt_base::ok)
    {
      ostr.write(t, tn - t);
    }
    else
    {
      ostr.setstate(std::ios_base::failbit);
    }
  }
  return ostr;
}

これを使えば、

std::cout << L'あ' << std::endl;

といったことが普通にできるようになります。ただし、ワイド文字周りのサポート状況は処理系によってかなり異なりますし、バグに遭遇する確率も高いので、使用する場合は十分ご注意ください。また、あらかじめ imbue メンバ関数によってロケールを設定する必要がありますので、お忘れなく。

103.3 cout にワイド文字列を出力するテンプレート

103.2 でご紹介したテンプレートは、1 文字しか出力できませんでした。実際には文字列を出力することが多いと思いますので、文字列版もご紹介しておきましょう。

template<class Traits>
std::basic_ostream<char, Traits>&
  operator<<(std::basic_ostream<char, Traits>& ostr, const wchar_t* wcs)
{
  typename std::basic_ostream<char, Traits>::sentry se(ostr);
  if (se)
  {
    typedef std::codecvt<wchar_t, char, std::mbstate_t> codecvt_t;
    const codecvt_t& cvt = std::use_facet<codecvt_t>(ostr.getloc());
    std::mbstate_t st;
    std::size_t length =  std::wcslen(wcs);
    char* t = new char[MB_LEN_MAX*length];
    const wchar_t* fn;
    char* tn;

    try
    {
      if (cvt.out(st, wcs, wcs+length, fn, t, t+MB_LEN_MAX*length, tn)
        == std::codecvt_base::ok)
      {
        ostr.write(t, tn - t);
      }
      else
      {
        ostr.setstate(std::ios_base::failbit);
      }
    }
    catch (...)
    {
      delete[] t;
      throw;
    }
    delete[] t;
  }
  return ostr;
}

これでワイド文字列を std::cout などの char ベースのストリームに直接出力できます。1 文字版のときと同様、imbue を呼び出すのを忘れないでください。

できれば、std::wstring 版もあった方がよいのでしょうが、やり方は同じですので、どうぞご自身で実装してみてください。

トラックバック


URL から "-nospam" を削除してトラックバックを送信してください。

std::wstring版の実装について

たかぎさんの実装を参考にしてstd::wstring版を実装してみたのですが、

class StringConvertException:public std::runtime_error{
public:
enum ConvertMode{
WtoA,
AtoW
};
StringConvertException(ConvertMode modeCode):std::runtime_error("文字コードの変換に失敗しました。"),mode(modeCode){}
ConvertMode GetMode()const{return mode;}
private:
ConvertMode mode;
};

template<typename Traits,typename Alloc>
std::basic_ostream<char,Traits>& operator<<(std::basic_ostream<char,Traits>& stream,const std::basic_string<wchar_t,Traits,Alloc>&wideString)
{
typename std::basic_ostream<char,Traits>::sentry se(stream);
if(!se)return stream;
typedef std::codecvt<wchar_t,char,std::mbstate_t> codecvt_t;
const codecvt_t& converter=std::use_facet<codecvt_t>(stream.getloc());
std::mbstate_t state=std::mbstate_t();
std::vectoroutbuf(wideString.length()*MB_LEN_MAX,'\0');
char *outend;
const wchar_t *inend;
if(converter.out(state,wideString.c_str(),wideString.c_str()+wideString.length(),inend,&(*outbuf.begin()),&(*outbuf.end()),outend)!=codecvt_t::ok){
stream.setstate(std::ios::failbit);
throw StringConvertException(StringConvertException::WtoA);
}
return stream.write(&outbuf[0],outend-(&outbuf[0]));
}

1.mbstate_tのオブジェクトの初期化はこれでいいのでしょうか?
2.変換後の出力バッファにstd::vectorを用いるのは安全でしょうか?
の2点について教えていただきたいのですが、よろしいでしょうか?

Re:std::wstring版の実装について

> 1.mbstate_tのオブジェクトの初期化はこれでいいのでしょうか?

問題ないと思います。

> 2.変換後の出力バッファにstd::vectorを用いるのは安全でしょうか?
の2点について教えていただきたいのですが、よろしいでしょうか?

std::vectorを使うこと自体は問題ありませんが、&(*outbuf.begin()) ではなく &outbuf[0] のほうがよいと思います。

あと、std::basic_ostream の Traits と、std::basic_string の Traits は同じにできませんので、このやり方には無理があります。
ここは単純に、

template <class Traits, class WTraits, class Allocator>
std::basic_ostream<char, Traits>&
operator<<(std::basic_ostream<char, Traits>& ostr, const std::basic_string<wchar_t, WTraits, Allocator>& str)
{
  return ostr << str.c_str();
}

とすればよいのではないでしょうか?

ありがとうございます。 これで少し安心しまし

ありがとうございます。
これで少し安心しました。

このエントリーを含むはてなブックマーク