65.9K
CodeProject 正在变化。 阅读更多。
Home

使 Managed Extensions for C++ STL 友好

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (17投票s)

2003 年 4 月 18 日

BSD

5分钟阅读

viewsIcon

188289

downloadIcon

926

一些代码可以帮助您组合 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

上述代码存在以下问题:

  1. 非托管类不能包含 __gc 指针。
  2. 内置的比较运算符不能用于排序托管对象。
  3. C++ 输出流无法与托管类型一起工作,因此我们无法使用 ostream_iterator
  4. 标准算法无法与 .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

从表中可以看出,为了使托管类型能够与 lessgreaterless_equalgreater_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 接口实现的,而不是 IListIDictionary。我计划在 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 算法。

参考文献

  1. C++ 托管扩展规范
  2. Jonathan Caves:《使用 C++ 标准库处理托管类型》- C/C++ Users Journal,2002 年 10 月。
© . All rights reserved.