使 Managed Extensions for C++ STL 友好






4.89/5 (17投票s)
一些代码可以帮助您组合 MC++ 和 STL。
引言
选择适合 .NET 的编程语言,很大程度上取决于个人偏好和需要解决的问题。对我而言,使用 C++ 的托管扩展(MC++)最重要的原因之一是能够重用“原生” C++ 代码,特别是 C++ 标准库。
在 2002 年 10 月的 C/C++ User Journal 杂志上,Jonathan Caves 撰写了一篇文章,描述了在使用 C++ 标准库处理托管类型时遇到的一些问题。我的目标是更进一步,提供一些代码,帮助开发人员轻松地集成托管类型和 STL。这些代码包含在一个名为 gcstl.h 的头文件中。
要使用我提供的代码,您需要具备 C++ 托管扩展和 STL 的工作知识。这意味着,至少您需要阅读过 C++ 托管扩展规范 和一些 STL 入门资料。要真正理解其内部原理,您需要熟悉 STL 的内部机制,并对 CLR 和 .NET Framework 有深入的了解。
问题是什么?
为了说明处理托管类型和 STL 时遇到的问题,请尝试编译以下代码片段:
#using <mscorlib.dll> #include <vector> #include <algorithm> #include <iterator> #include <iostream> using namespace System; using namespace std; int main() { vector <String __gc*> v; //Problem 1 v.push_back(S"bca"); v.push_back(S"bac"); v.push_back(S"abc"); sort (v.begin(), v.end()); //Problem 2 copy (v.begin(), v.end(), ostream_iterator<String __gc*>(wcout, L" ")); //Problem 3 }
另外,如果我们可以使用 STL 算法处理 .NET 集合,那将是非常方便的。例如:
String __gc* st_array[] = {S"bca", S"bac", S"abc"}; std::find(st_array[0], st_array[3], S"abc"); //Problem 4
上述代码存在以下问题:
- 非托管类不能包含
__gc
指针。 - 内置的比较运算符不能用于排序托管对象。
- C++ 输出流无法与托管类型一起工作,因此我们无法使用
ostream_iterator
。 - 标准算法无法与 .NET 集合一起工作。
让我们逐个讨论这些问题。
问题 1:在 STL 容器中存储 __gc 指针
正如《C++ 托管扩展规范》第 16.3 章所述,“非托管类的成员声明为 __gc
指针类型是非法的。”然而,存在 System::Runtime::InteropServices::GCHandle
类,它允许从非托管堆引用托管对象。更好的是,VC.NET 团队的好心人提供了一个包装器模板 gcroot
,可以轻松地处理 GCHandle
。只需包含 vcclr.h 并像这样使用它:
#using <mscorlib.dll> #include <vector> #include <algorithm> #include <iterator> #include <iostream> #include <vcclr.h> using namespace System; using namespace std; int main() { //vector <String __gc*> v; //Problem 1 vector <gcroot<String __gc*> > v; v.push_back(S"bca"); v.push_back(S"bac"); v.push_back(S"abc"); /* sort (v.begin(), v.end()); //Problem 2 copy (v.begin(), v.end(), ostream_iterator<String __gc*>(wcout, S" ")); //Problem 3 */ }
要了解 gcroot
的内部机制,请查看头文件 gcroot.h。
问题 2:托管对象的比较
要使用 STL 算法以及排序容器,您必须能够比较对象的值。STL 非常依赖值语义,并且通常建议避免使用指针(无论是 __gc
还是 __nogc
),但通过使用我提供的 gcstl.h 头文件中的一些函数对象,可以轻松地处理托管对象。
#using <mscorlib.dll> #include <vector> #include <algorithm> #include <iterator> #include <iostream> #include "gcstl.h" using namespace System; using namespace std; int main() { //vector <String __gc*> v; //Problem 1 vector <gcroot<String __gc*> > v; v.push_back(S"bca"); v.push_back(S"bac"); v.push_back(S"abc"); //sort (v.begin(), v.end()); //Problem 2 sort (v.begin(), v.end(), gc_less<String __gc*>()); /* copy (v.begin(), v.end(), ostream_iterator<String __gc*>(wcout, S" ")); //Problem 3 */ }
请注意,我们现在使用的是 sort 的版本,它使用用户定义的谓词函数对象来定义比较标准。大多数 STL 算法都有版本允许用户提供比较谓词。
在头文件 gcstl.h 中,我定义了与标准头文件 <functional> 中定义的函数对象对应的、适用于 __gc
指针的比较函数对象。在使用这些函数对象时,请务必将其与算法以及 set 和 map 等排序容器一起使用。
set<gcroot<String __gc*>, gc_less<String __gc*>()> my_set;
而不是
set<gcroot<String __gc*> > my_set;
实际上,两个版本都可以编译,但在后一种情况下,排序将没有意义。
gcstl.h 中定义的函数对象如下表所示:
标准版本 | gc 版本 | 实现方式 |
---|---|---|
equal_to |
gc_equal_to |
System::Object::Equals |
not_equal_to |
gc_not_equal_to |
System::Object::Equals |
less |
gc_less |
System::IComparable::CompareTo |
greater |
gc_greater |
System::IComparable::CompareTo |
less_equal |
gc_less_equal |
System::IComparable::CompareTo |
greater_equal |
gc_greater_equal |
System::IComparable::CompareTo |
从表中可以看出,为了使托管类型能够与 less
、greater
、less_equal
和 greater_equal
一起工作,它必须实现 IComparable
接口。
问题 3:ostream_iterator 的替代方案
我经常使用 ostring_stream
将我的 STL 容器的内容转储到屏幕或文件中。因此,我认为拥有一个具有类似语义的输出迭代器,该迭代器内部可以与 .NET 的 System::IO::TextWriter
一起工作,会很有用。于是我创建了 textwriter_iterator
。如下所示:
#include <vector> #include <algorithm> #include <iterator> #include "gcstl.h" using namespace System; using namespace std; int main() { //vector <String __gc*> v; //Problem 1 vector <gcroot<String __gc*> > v; v.push_back(S"bca"); v.push_back(S"bac"); v.push_back(S"abc"); //sort (v.begin(), v.end()); //Problem 2 sort (v.begin(), v.end(), gc_less<String __gc*>()); //copy (v.begin(), v.end(), //ostream_iterator<String __gc*>(wcout, S" ")); //Problem 3 copy (v.begin(), v.end(), textwriter_iterator<String __gc*> (Console::Out, S" ")); }
在内部,textwriter_iterator
使用 Object::ToString()
将对象写入文本写入器。
请注意,我没有为 istream_operator
提供托管版本。原因是,在我平时的 C++ 编程中,我并未发现这个运算符有多大用处,而且它的实现并非易事,所以我决定跳过它。
问题 4:将 STL 算法用于 .NET 集合
为了让 STL 算法能够处理 .NET 集合,我创建了一个模板包装器 gc_collection
。下面是一个如何使用此包装器的示例:
#include <algorithm> #include "gcstl.h" using namespace System; using namespace std; int main() { String __gc* st_array[] = {S"bca", S"bac", S"abc"}; gc_collection<Array __gc*, String __gc*> cont(st_array); gc_collection<Array __gc*, String __gc*>::const_iterator it = find_if(cont.begin(), cont.end(), bind2nd(gc_equal_to<gcroot<String __gc*> >(),S"abc")); if (it != cont.end()) Console::WriteLine(*it); }
gc_collection
有两个模板参数:Container
(默认为 System::Collections::ICollection __gc*
)- 被包装容器的类型;Elem
(默认为 System::Object __gc*
)- 包含的元素的类型。
我设计 gc_collection
的目标是覆盖广泛的 .NET 集合,而不是拥有过多的功能。因此,它是基于 ICollection
接口实现的,而不是 IList
或 IDictionary
。我计划在 gcstl.h 的下一个版本中为这些接口创建包装器。
gc_collection
具有以下成员函数:
gc_collection (Container container) |
构造函数 |
const_iterator begin() |
第一个元素 |
const_iterator end() |
越界元素 |
int size() const |
元素数量 |
bool empty() const |
集合是否为空? |
operator Container() const |
转换为被包装的容器类型 |
Container operator->() const |
允许将 gc_collection 用作智能指针 |
gc_collection::const_iterator
甚至不是一个 前向常量迭代器 - 它缺少后置自增运算符。它基于 IEnumerator
接口实现。
尽管 gc_collection
很精简,但它使我们能够处理大多数非变异性的 STL 算法。
参考文献
- C++ 托管扩展规范
- Jonathan Caves:《使用 C++ 标准库处理托管类型》- C/C++ Users Journal,2002 年 10 月。