はじめに
赤坂さんが面白いアーティクルを寄稿してくれました。
Visual C++ 2008で新たに追加されたSTL/CLRはなんとも不思議なライブラリです。.NET FrameworkのBCL(Base Class Library)が提供するSystem.Collections.Generic
名前空間で定義されたBCLコレクションと同等(それ以上?)のクラス群を提供し、それらは標準C++ライブラリに組み入れられたSTLとの高い互換性を実現しています。その代償として、templateで実装されているためにアセンブリの外に持ち出すことができません(C#やVBはtemplateを理解できませんから)。
赤坂さんのアーティクルでは、STL/CLRコンテナがinterfaceを実装していることを利用してアセンブリ外からinterfaceとしてアクセスする方法について解説しています。
本稿ではSTL/CLRの持つさまざまな特徴と、STL/CLRがBCLコレクションのために提供するヘルパ関数/クラスについて解説します。
STL/CLRの特徴
と、その前に。STL/CLRを使ったコードをコンパイルすると、次のようなエラー・メッセージが現れます:
fatal error C1107: アセンブリ 'Microsoft.VisualC.STLCLR.dll' がみつ かりませんでした: /AI または LIBPATH 環境変数を使用してアセンブリ検 索パスを指定してください。
このエラーは、ソースコードが間接的に#includeしているヘッダ<xutility>内で#using <Microsoft.VisualC.STLCLR.dll>
されており、コンパイラがアセンブリ「Microsoft.VisualC.STLCLR.dll」を見つけられなかったことに起因します。「Microsoft.VisualC.STLCLR.dll」はVC++がインストールされたディレクトリのlibサブ・ディレクトリにあるので、プロジェクト・プロパティで[#using参照の解決]に$(VCInstallDir)lib
を設定することでエラーを解決できます。
さて、それではSTL/CLRにまつわるさまざまなトピックを紹介していきましょうか。
STL/CLRはSTLと同様のクラス/関数を提供する
STL/CLRが作られた最大の目的が「C++/CLIにSTLを提供する」ことですから、これは至極当然のことです。クラス名/関数名はもちろんのこと、ヘッダ名までSTLと同じ名前が用いられています。STLと異なるのは名前空間がstd
ではなくcliext
であること、ヘッダがcliextサブディレクトリに置かれていること、などなど。
#include <iostream> #include <vector> #include <string> int main() { std::vector<std::string> sv; sv.push_back("zero"); // 末尾に追加 sv.push_back("two"); sv.push_back("THREE"); sv.insert(sv.begin()+1, "one"); // 挿入 sv[3] = "three"; // operator[]で3番目を更新 sv.erase(sv.begin()+2); // iteratorの指す要素を削除 // 全要素を列挙 for ( std::vector<std::string>::iterator iter = sv.begin(); iter != sv.end(); ++iter ) { std::cout << *iter << std::endl; } sv.clear(); // 全要素削除 std::cout << "要素数: " << sv.size() << std::endl; }
このC++コードをSTL/CLRを使ってC++/CLIにポートすると次のようになります。STLとの互換性が十分考慮されているので、STLになじみのあるC++プログラマには敷居の低いライブラリに仕上がっています。
#include <cliext/vector> using namespace System; int main() { cliext::vector<String^> sv; sv.push_back(L"zero"); // 末尾に追加 sv.push_back(L"two"); sv.push_back(L"THREE"); sv.insert(sv.begin()+1,L"one"); // 挿入 sv[3] = L"three"; // operator[]で3番目を更新 sv.erase(sv.begin()+2); // iteratorの指す要素を削除 // 全要素を列挙 for ( cliext::vector<String^>::iterator iter = sv.begin(); iter != sv.end(); ++iter ) { Console::WriteLine(*iter); } sv.clear(); // 全要素削除 Console::WriteLine(L"要素数: {0}", sv.size()); }
STL/CLRコンテナの特徴の一つにメソッドの統一性があります。これもSTLとの互換性によるものです。シーケンシャルな集合から特定の要素を見つけ、そこから集合の最後までを列挙するコードを考えてみましょう。.NET FrameworkのBCLコレクション:List<T>
(可変長配列)とLinkedList<T>
(双方向リスト)を使ったコードはそれぞれ次のようになります。
using namespace System; using namespace System::Collections::Generic; bool is_three(String^ s) { return s == L"three"; } int main() { array<String^>^ source = gcnew array<String^> { L"zero", L"one", L"two", L"three", L"four", L"five" }; /* * 集合から"three"を探し、そこから最後までを列挙する */ // BCLコレクション: List と LinkedList の場合: { // BCL List<String^> List<String^> container(source); // 何番目にあるかを調べ int result = container.FindIndex( gcnew Predicate<String^>(&is_three)); if ( result >= 0 ) { // 見つかったら while ( result < container.Count ) { // そこから最後まで列挙 Console::WriteLine(container[result]); ++result; } } } { // BCL LinkedList<String^> LinkedList<String^> container(source); // 目的の値を指す LinkedListNode を手にいれ、 LinkedListNode<String^>^ result = container.Find(L"three"); while ( result != nullptr ) { // そこから最後まで列挙 Console::WriteLine(result->Value); result = result->Next; } } }
List<T>
とLinkedList<T>
では要素の検索と列挙のやり方が異なっています。STL/CLRコンテナではどうでしょうか。
#include <cliext/vector> #include <cliext/list> #include <cliext/algorithm> using namespace System; using namespace System::Collections::Generic; bool is_three(String^ s) { return s == L"three"; } int main() { array<String^>^ source = gcnew array<String^> { L"zero", L"one", L"two", L"three", L"four", L"five" }; /* * 集合から"three"を探し、そこから最後までを列挙する */ // STL/CLRコンテナ: vector と list の場合 { // STL/CLR vector cliext::vector<String^> container(source); // 目的の値を指すイテレータを手にいれ cliext::vector<String^>::iterator iter = cliext::find(container.begin(), container.end(), gcnew String(L"three")); while ( iter != container.end() ) { // そこから最後までを列挙 Console::WriteLine(*iter); ++iter; } } { // STL/CLR list cliext::list<String^> container(source); // 目的の値を指すイテレータを手にいれ cliext::list<String^>::iterator iter = cliext::find(container.begin(), container.end(), gcnew String(L"three")); while ( iter != container.end() ) { // そこから最後までを列挙 Console::WriteLine(*iter); ++iter; } } }
vector<T>
,list<T>
の検索と列挙はどちらもまったく同じ書式になっていますね。