STLとオブジェクト指向

STLの提供する様々なコンポーネントについて、例を挙げながら解説してきました。STLを使ったプログラミング・スタイルに違和感を覚えるプログラマも少なくないでしょう。特にオブジェクト指向プログラミングに慣れ親しんだプログラマには大きな思想の違いを感じることと思います。

オブジェクト指向プログラミングはデータの抽象化、すなわちモノに対する操作の集合でオブジェクトを定義することで、モノに対して何ができるかを公開し、モノがどのようなデータで構成されているかを隠します。そのためにデータとアルゴリズムを一体化します。それに対しSTLはアルゴリズムとデータを分離し、両者の自由な組み合わせを可能にします。

しかしながらSTLはオブジェクト指向プログラミングと相容れないわけではありません。STLをオブジェクトを実装するための"ネジ・クギ"として利用することで、利用者にはオブジェクトとして扱うことができ、実装者にはSTLの自由度とパワーを享受できます。コンテナの解説で例示した電話番号簿をクラス化し、その実装にSTLを利用したコードを示します。

/*
 * phonebook.cpp
 *   vector を使った簡単な電話番号簿
 */

#include <iostream>
#include <fstream>
#include <string>
#include <list>
#include <vector>
#include <iterator>
#include <algorithm>

using namespace std;

/*
 * PhoneBook :
 *   利用者にはこれがSTLで実装されていることを意識させない
 *   そのために電話番号簿に必要なインタフェースのみを公開する。
 */
class PhoneBook {

  // struct record は PhoneBookの利用者には公開しない。
  struct record {
    record() {}
    string name;
    string phone;
    record(const string& n, const string& p) : name(n), phone(p) {}
    record(const string& n) : name(n) {}
    friend bool operator==(const record& x, const record& y)
      { return x.name == y.name; }
    friend bool operator<(const record& x, const record& y)
      { return x.name < y.name; }
    friend ostream& operator<<(ostream& strm,const record& r)
      { return strm << r.name << " : " << r.phone; }
    friend istream& operator>>(istream& strm, record& r)
      { char dummy; return strm >> r.name >> dummy >> r.phone; }
  };

  typedef vector<record> book_type;
  book_type book;

public:
  void load(const string& fname);
  void store(const string& fname) const;
  void add(const string& name, const string& phone);
  bool contains(const string& name) const;
  void remove(const string& name);
  bool get(const string& name, string& phone) const;
  void print(ostream& out) const;
  void sort();
  int  entries() const;
  void clear();
};

// ファイルから読み込む
void PhoneBook::load(const string& fname) {
  ifstream in(fname.c_str());
  if ( in.is_open() ) {
    copy(istream_iterator<record>(in),istream_iterator<record>(),
         inserter(book,book.begin()));
    in.close();
  }
}

// ファイルに書き込む
void PhoneBook::store(const string& fname) const {
  ofstream out(fname.c_str());
  if ( out.is_open() ) {
    copy(book.begin(), book.end(), ostream_iterator<record>(out,"\n"));
    out.close();
  }
}

// 登録
void PhoneBook::add(const string& name, const string& phone) {
  if ( !contains(name) )
    book.push_back(record(name,phone));
}

// nameが登録されていればtrue
bool PhoneBook::contains(const string& name) const {
  return find(book.begin(), book.end(), name) != book.end();
}

// nameを削除
void PhoneBook::remove(const string& name) {
  book_type::iterator i = find(book.begin(), book.end(), name);
  if ( i != book.end() )
    book.erase(i);
}

// 検索
bool PhoneBook::get(const string& name, string& phone) const {
  book_type::const_iterator i = find(book.begin(), book.end(), name);
  if ( i != book.end() ) {
    phone = i->phone;
    return true;
  }
  return false;
}

// プリント
void PhoneBook::print(ostream& out) const {
  copy(book.begin(), book.end(), ostream_iterator<record>(out,"\n"));
}

// ソート
void PhoneBook::sort() {
  std::sort(book.begin(), book.end());
}

// 登録されているレコード数
int  PhoneBook::entries() const {
  return book.size();
}

// クリア
void PhoneBook::clear() {
  book.clear();
}

// プロンプト
char prompt() {
  string command;
  cout << "add/delete/find/list/clear/quit [a,d,f,l,c,q] ?" << flush;
  cin >> command;
  return command[0];
}

int main() {

  PhoneBook book;

  // 電話番号簿をファイルから読み出す
  cout << "loading file..." << endl;
  book.load("phonebook.txt");

  bool quit = false;
  do {
    string name;
    string phone;
    switch ( prompt() ) {
    // 追加
    case 'a' : case 'A' : {
      cout << "name:" << flush;
      cin >> name;
      // 登録されていないことを確認する
      if ( book.contains(name) ) {
        cout << "already exists." << endl;
      } else {
        cout << "phone:" << flush;
        cin >> phone;
        // レコードを追加する
        book.add(name,phone);
      }
      break;
    }
    // 削除
    case 'd' : case 'D' : {
      cout << "name:" << flush;
      cin >> name;
      // 登録されていればそれを削除する
      if ( !book.contains(name) ) {
        cout << "not found." << endl;
      } else {
        book.remove(name);
      }

      break;
    }
    // 検索
    case 'f' : case 'F' : {
      cout << "name:" << flush;
      cin >> name;
      // 登録されていればそのレコードを出力する
      string phone;
      if ( !book.get(name,phone) ) {
        cout << "not found." << endl;
      } else {
        cout << name << " : " << phone << endl;
      }
      break;
    }
    // リスト
    case 'l' : case 'L' : {
      cout << book.entries() << "entries:" << endl;
      book.sort();
      book.print(cout);
      break;
    }
    // クリア
    case 'c' : case 'C' : {
      book.clear();
      break;
    }
    // 終了
    case 'q' : case 'Q' : {
      quit = true;
      break;
    }
    default :
      cout << '?' << endl;
    }
  } while ( !quit );

  // 電話番号簿をファイルに書き込む
  cout << "saving file..." << endl;
  book.store("phonebook.txt");

  return 0;
}