集合(C++/CX)

在 C++/CX 程序中,你可以任意使用标准模板库 (STL) 容器或任何其他用户定义的集合类型。 但是,当你跨 Windows 运行时应用程序二进制接口 (ABI) 边界来回传递集合时,例如传递到 XAML 控件或 JavaScript 客户端时,必须使用 Windows 运行时集合类型。

Windows 运行时定义集合和相关类型的接口,并且,C++/CX 在 collection.h 头文件中提供具体的 C++ 实现。 下图显示了各个集合类型之间的关系:

C++ CX 继承树的集合类型图解。

矢量使用情况

当类必须将序列容器传递给另一个 Windows 运行时组件时,请将其 Windows::Foundation::Collections::IVector<T> 用作参数或返回类型,并 Platform::Collections::Vector<T> 用作具体实现。 如果你尝试在公共返回值或参数中使用 Vector 类型,则将引发编译器错误 C3986。 可以通过将 Vector 更改为 IVector来修复该错误。

Important

如果您要在自己的程序中传递序列,请使用 Vectorstd::vector ,因为它们比 IVector更高效。 在通过 ABI 传递容器时,仅使用 IVector

Windows 运行时类型系统不支持交错数组的概念,因此无法将 IVector<Platform::Array<T>> 作为返回值或方法参数传递。 要跨 ABI 传递交错数组或一系列序列,请使用 IVector<IVector<T>^>

Vector<T> 提供添加、移除和访问集合项所需的方法,并且,它可以隐式转换为 IVector<T>。 还可以对 Vector<T>的实例使用 STL 算法。 下面的示例演示一些基本用法。 此处的begin函数end函数来自Platform::Collections命名空间,而不是std命名空间。

#include <collection.h>
#include <algorithm>
using namespace Platform;
using namespace Platform::Collections;
using namespace Windows::Foundation::Collections;


void Class1::Test()
{
    Vector<int>^ vec = ref new Vector<int>();
    vec->Append(1);
    vec->Append(2);
    vec->Append(3);
    vec->Append(4);
    vec->Append(5);


    auto it = 
        std::find(begin(vec), end(vec), 3);

    int j = *it; //j = 3
    int k = *(it + 1); //or it[1]

    // Find a specified value.
    unsigned int n;         
    bool found = vec->IndexOf(4, &n); //n = 3

    // Get the value at the specified index.
    n = vec->GetAt(4); // n = 3

    // Insert an item.
    // vec = 0, 1, 2, 3, 4, 5
    vec->InsertAt(0, 0);

    // Modify an item.
    // vec = 0, 1, 2, 12, 4, 5,
    vec->SetAt(3, 12);

    // Remove an item.
    //vec = 1, 2, 12, 4, 5 
    vec->RemoveAt(0);

    // vec = 1, 2, 12, 4
    vec->RemoveAtEnd();

    // Get a read-only view into the vector.
    IVectorView<int>^ view = vec->GetView();
}

如果你具有使用 std::vector 的现有代码,而你想在 Windows 运行时组件中重用它,只需使用 Vector 构造函数之一即可,它采用一个 std::vector 或一对迭代器在你将集合传递过 ABI 之处构造一个 Vector。 下面的示例演示如何从 Vectorstd::vector移动构造函数用于高效初始化。 移动操作完成后,原始的 vec 变量不再有效。

//#include <collection.h>
//#include <vector>
//#include <utility> //for std::move
//using namespace Platform::Collections;
//using namespace Windows::Foundation::Collections;
//using namespace std;
IVector<int>^ Class1::GetInts()
{
    vector<int> vec;
    for(int i = 0; i < 10; i++)
    {
        vec.push_back(i);
    }    
    // Implicit conversion to IVector
    return ref new Vector<int>(std::move(vec));
}

如果您有一个必须在将来某个时间点通过 ABI 传递的字符串向量,则您必须确定最初是将字符串创建为 std::wstring 类型还是 Platform::String^ 类型。 如果你必须对字符串执行大量处理,请使用 wstring。 否则,请将字符串创建为 Platform::String^ 类型,这可避免稍后转换它们的开销。 你还必须确定是将这些字符串内部置于 std::vector 还是 Platform::Collections::Vector 中。 作为一种常规做法,先使用 std::vector ,然后在通过 ABI 传递容器时从其创建一个 Platform::Vector

Vector 中的值类型

任何存储在 Platform::Collections::Vector 中的元素都必须支持相等比较,无论是隐式地还是通过使用您提供的自定义 std::equal_to 比较器。 所有引用类型和所有标量类型都隐式支持相等性比较。 对于非标量值类型(例如 Windows::Foundation::DateTime,或自定义比较 objA->UniqueID == objB->UniqueID),必须提供自定义函数对象。

VectorProxy 元素

Platform::Collections::VectorIteratorPlatform::Collections::VectorViewIterator启用使用range for容器的std::sort循环和IVector<T>算法。 但是IVector元素无法通过C++指针解引用来访问,只能通过GetAtSetAt方法访问。 因此,这些迭代器使用代理类Platform::Details::VectorProxy<T>Platform::Details::ArrowProxy<T>,根据标准库的要求通过*->[]操作符提供对各个元素的访问。 严格来说,给定 IVector<Person^> vec*begin(vec) 的类型为 VectorProxy<Person^>。 不过,代理对象对于你的代码几乎始终是透明的。 我们不讨论这些代理对象,因为它们仅由迭代器在内部使用,但了解该机制的工作原理将非常有用。

for 容器使用基于范围的 IVector 循环时,请使用 auto&& 以使迭代器变量正确绑定到 VectorProxy 元素。 如果使用 auto&,将引发编译器警告 C4239 ,并在 VectorProxy 警告文本中提及。

下图演示了针对 range forIVector<Person^>循环。 请注意,执行操作在第 64 行的断点处停止。 “快速监视”窗口显示迭代器变量p实际上是一个VectorProxy<Person^>,它具有m_vm_i成员变量。 不过,当调用 GetType 时,它将返回与 Person 实例 p2相同的类型。 要点是,虽然 VectorProxyArrowProxy 可能出现在 QuickWatch、调试器中的某些编译器错误或其他位置,但通常不必为它们显式编写代码。

在基于循环的范围中调试 VectorProxy 的屏幕截图。

必须针对代理对象进行编码的一种情况是,你必须对元素执行 dynamic_cast ,例如你在 UIElement 元素集合中寻找特定类型的 XAML 对象。 在这种情况下,必须先将元素类型转换为 Platform::Object^,然后执行动态类型转换。

void FindButton(UIElementCollection^ col)
{
    // Use auto&& to avoid warning C4239
    for (auto&& elem : col)
    {
        Button^ temp = dynamic_cast<Button^>(static_cast<Object^>(elem));
        if (nullptr != temp)
        {
            // Use temp...
        }
    }
}

映射使用情况

此示例演示如何插入项目、在 Platform::Collections::Map 中查找它们,然后将 Map 返回为只读 Windows::Foundation::Collections::IMapView 类型。

//#include <collection.h>
//using namespace Platform::Collections;
//using namespace Windows::Foundation::Collections;
IMapView<String^, int>^ Class1::MapTest()
{
    Map<String^, int>^ m = ref new Map<String^, int >();
    m->Insert("Mike", 0);
    m->Insert("Dave", 1);
    m->Insert("Doug", 2);
    m->Insert("Nikki", 3);
    m->Insert("Kayley", 4);
    m->Insert("Alex", 5);
    m->Insert("Spencer", 6);

   // PC::Map does not support [] operator
   int i = m->Lookup("Doug");
   
   return m->GetView();
   
}

通常,对于内部映射功能,出于性能考虑,应尽可能首选 std::map 类型。 如果必须通过 ABI 传递容器,请从Platform::Collections::Map中构造一个std::map并返回Map为一个 Windows::Foundation::Collections::IMap。 如果你尝试在公共返回值或参数中使用 Map 类型,则将引发编译器错误 C3986。 可以通过将 Map 更改为 IMap来修复该错误。 在某些情况下,例如,如果您不需要大量查找或插入,而需要通过 ABI 频繁传递集合,则在一开始使用 Platform::Collections::Map 可能成本较低,同时可避免转换 std::map的开销。 在任何情况下,应避免在 IMap 上执行查找和插入操作,因为这些是三种类型中性能最差的操作。 仅在你通过 ABI 传递容器之处转换为 IMap

Map 中的值类型

Platform::Collections::Map 中的元素是有序的。 要存储在某个元素 Map 中的任何元素都必须支持与严格弱排序的不相比较,无论是隐式的,还是使用你提供的自定义 std::less 比较器。 标量类型隐式支持比较。 对于非标量类型(如 Windows::Foundation::DateTime)或自定义比较(如 objA->UniqueID < objB->UniqueID),你必须提供自定义比较器。

集合类型

集合分为四种类别:序列集合和关联集合各自的可修改版本和只读版本。 此外,C++/CX 通过提供简化集合访问的三个迭代器类增强集合。

可修改集合的元素可以更改,但只读集合的元素(称为 视图)只能读取。 Platform::Collections::VectorPlatform::Collections::VectorView集合的元素可以通过使用迭代器或集合的Vector::GetAt和索引来访问。 可以使用集合的 Map::Lookup 元素和键访问关联集合的元素。

Platform::Collections::Map
可修改的关联集合。 映射元素为键/值对。 支持查找键以检索其关联值,也支持遍历所有键值对。

MapMapView<K, V, C = std::less<K>>上模板化,因此,你可以自定义比较器。 此外, VectorVectorView<T, E = std::equal_to<T>> 上模板化,以便你可以自定义 IndexOf()的行为。 这通常对于值结构的 VectorVectorView 非常重要。 例如,若要创建, Vector<Windows::Foundation::DateTime>必须提供自定义比较器,因为 DateTime 不会重载 == 运算符。

Platform::Collections::MapView
Map的只读版本。

Platform::Collections::Vector
可修改的序列集合。 Vector<T> 支持常量时间随机访问和摊销至常量时间 Append 操作。

Platform::Collections::VectorView
Vector的只读版本。

Platform::Collections::InputIterator
满足 STL 输入迭代器要求的 STL 迭代器。

Platform::Collections::VectorIterator
满足 STL 可变随机访问迭代器要求的 STL 迭代器。

Platform::Collections::VectorViewIterator
满足 STL const 随机访问迭代器要求的 STL 迭代器。

begin() 和 end() 函数

为了简化 STL 的处理VectorVectorViewMapMapView和任意Windows::Foundation::Collections对象的使用,C++/CX 支持函数和非begin成员函数的end重载。

下表列出可用迭代器和函数。

Iterators Functions
Platform::Collections::VectorIterator<T>

(内部存储 Windows::Foundation::Collections::IVector<T>int。)
begin / endWindows::Foundation::Collections::IVector<T>
Platform::Collections::VectorViewIterator<T>

(内部存储 IVectorView<T>^ 和 int.)
begin / endIVectorView<T>^)
Platform::Collections::InputIterator<T>

(内部存储 IIterator<T>^ 和 T.)
begin / endIIterable<T>
Platform::Collections::InputIterator<IKeyValuePair<K, V>^>

(内部存储 IIterator<T>^ 和 T.)
begin / endIMap<K,V>
Platform::Collections::InputIterator<IKeyValuePair<K, V>^>

(内部存储 IIterator<T>^ 和 T.)
begin / endWindows::Foundation::Collections::IMapView

集合更改事件

VectorMap 支持 XAML 集合中的数据绑定,方式是通过实现在更改或重置集合对象,或在插入、移除或更改集合的任何元素时发生的事件。 您可编写自己的支持数据绑定的类型,尽管因这些类型是密封类型而导致无法从 MapVector 继承。

这些Windows::Foundation::Collections::VectorChangedEventHandlerWindows::Foundation::Collections::MapChangedEventHandler委托用于为集合更改事件指定事件处理程序的签名。 Windows::Foundation::Collections::CollectionChange公共枚举类和 Platform::Collections::Details::MapChangedEventArgsPlatform::Collections::Details::VectorChangedEventArgs ref 类存储事件参数以确定导致事件的原因。 *EventArgs 类型是在 Details 命名空间中定义的,因为你在使用 MapVector 时无需显式构造或使用它们。

另请参阅

类型系统
C++/CX 语言参考
命名空间参考