Коллекции с помощью C++/WinRT

Внутри коллекции среда выполнения Windows много сложных движущихся частей. Но если вы хотите передать объект коллекции в функцию среда выполнения Windows или реализовать собственные свойства коллекции и типы коллекций, существуют функции и базовые классы в C++/WinRT для поддержки вас. Эти функции избавляют вас от лишней сложности и экономят вам много времени и усилий.

IVector — это интерфейс среда выполнения Windows, реализованный любой коллекцией элементов произвольного доступа. Если бы вы хотели реализовать IVector самостоятельно, вам также потребуется реализовать IIterable, IVectorView и IIterator. Даже если вам нужен собственный тип коллекции, это потребует больших усилий. Но если у вас есть данные в std::vector (или std::map, или std::unordered_map), а все, что вы хотите сделать, — передать это в API среда выполнения Windows, то вы хотите избежать этого уровня работы, если это возможно. И это возможно избежать, потому что C++/WinRT помогает эффективно создавать коллекции без особых усилий.

Также см. элементы управления XAML; привязка к коллекции C++/WinRT.

Вспомогательные функции для коллекций

Коллекция общего назначения, пустая

В этом разделе описывается сценарий, в котором вы хотите создать коллекцию, которая изначально пуста; а затем заполните его после создания.

Чтобы получить новый объект типа, реализующего коллекцию общего назначения, можно вызвать шаблон функции winrt::single_threaded_vector . Объект возвращается как IVector, и это интерфейс, через который вы вызываете функции и свойства возвращаемого объекта.

Если вы хотите напрямую скопировать и вставить следующие примеры кода в основной файл исходного кода проекта Windows Console Application (C++/WinRT), сначала задайте в свойствах проекта параметр Not Using Precompiled Headers.

// main.cpp
#include <winrt/Windows.Foundation.Collections.h>
#include <iostream>
using namespace winrt;

int main()
{
    winrt::init_apartment();

    Windows::Foundation::Collections::IVector<int> coll{ winrt::single_threaded_vector<int>() };
    coll.Append(1);
    coll.Append(2);
    coll.Append(3);

    for (auto const& el : coll)
    {
        std::cout << el << std::endl;
    }

    Windows::Foundation::Collections::IVectorView<int> view{ coll.GetView() };
}

Как видно в приведенном выше примере кода, после создания коллекции можно добавлять элементы, выполнять итерацию по ним и рассматривать объект как любой объект среда выполнения Windows коллекции, который вы могли бы получить из API. Если вам нужно неизменяемое представление по коллекции, можно вызвать IVector::GetView, как показано ниже. Приведенный выше шаблон — создание и использование коллекции — подходит для простых сценариев, в которых требуется передать данные или получить данные из API. Вы можете передать IVector или IVectorView везде, где ожидается IIterable.

В приведенном выше примере кода вызов winrt::init_apartment инициализирует поток в среда выполнения Windows; по умолчанию в многопоточной квартире. Вызов также инициализирует COM.

Универсальная коллекция, инициализированная на основе данных

В этом разделе рассматривается сценарий, в котором необходимо создать коллекцию и заполнить ее одновременно.

Вы можете избежать накладных расходов, связанных с вызовами Append, в предыдущем примере кода. Возможно, у вас уже есть исходные данные или вы можете заполнить исходные данные заранее при создании объекта коллекции среда выполнения Windows. Вот как это сделать.

auto coll1{ winrt::single_threaded_vector<int>({ 1,2,3 }) };

std::vector<int> values{ 1,2,3 };
auto coll2{ winrt::single_threaded_vector<int>(std::move(values)) };

for (auto const& el : coll2)
{
    std::cout << el << std::endl;
}

Можно передать временный объект, содержащий ваши данные, в winrt::single_threaded_vector, как и выше, с помощью coll1. Вы также можете переместить std::vector (предполагая, что вы снова не будете обращаться к ней) в функцию. В обоих случаях вы передаете rvalue в функцию. Это позволяет компилятору быть эффективным и избегать копирования данных. Если вы хотите узнать больше о rvalues, ознакомьтесь с категориями значений и ссылками на них.

Если вы хотите привязать элемент управления элементами XAML к коллекции, можно. Но имейте в виду, что, чтобы правильно задать свойство ItemsControl.ItemsSource, необходимо присвоить ему значение типа IVector из IInspectable (или типа взаимодействия, например IBindableObservableVector).

Ниже приведен пример кода, который создает коллекцию типа, подходящего для привязки, и добавляет к нему элемент. Контекст для этого примера кода можно найти в разделе элементы управления элементами XAML; привязка к коллекции C++/WinRT.

auto bookSkus{ winrt::single_threaded_vector<Windows::Foundation::IInspectable>() };
bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"Moby Dick"));

Вы можете создать коллекцию среда выполнения Windows из данных и получить её представление, готовое к передаче в API, не копируя данные.

std::vector<float> values{ 0.1f, 0.2f, 0.3f };
Windows::Foundation::Collections::IVectorView<float> view{ winrt::single_threaded_vector(std::move(values)).GetView() };

В приведенных выше примерах созданная коллекция может быть привязана к элементу управления элементами XAML; но коллекция не наблюдается.

Наблюдаемая коллекция

Чтобы получить новый объект типа, реализующего наблюдаемую коллекцию, вызовите шаблон функции winrt::single_threaded_observable_vector с любым типом элемента. Но чтобы сделать наблюдаемую коллекцию подходящей для привязки к элементу управления элементами XAML, используйте IInspectable в качестве типа элемента.

Объект возвращается как IObservableVector, и это интерфейс, с помощью которого вы (или элемент управления, к которому она привязана), вызывают функции и свойства возвращаемого объекта.

auto bookSkus{ winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>() };

Дополнительные сведения и примеры кода о привязке элементов управления пользовательского интерфейса (UI) к наблюдаемой коллекции см. в статье Элементы управления элементами XAML; привязка к коллекции C++/WinRT.

Ассоциативная коллекция (карта)

Существуют ассоциативные версии коллекций двух функций, которые мы рассмотрели.

При необходимости эти коллекции можно предварительно заполнить данными, передав в функцию rvalue типа std::map или std::unordered_map.

auto coll1{
    winrt::single_threaded_map<winrt::hstring, int>(std::map<winrt::hstring, int>{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    })
};

std::map<winrt::hstring, int> values{
    { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
};
auto coll2{ winrt::single_threaded_map<winrt::hstring, int>(std::move(values)) };

Однопотоковый

Слово "single-threaded" в названиях этих функций означает, что они не поддерживают конкурентное выполнение — иными словами, они не являются потокобезопасными. Упоминание потоков не связано с квартирами, так как объекты, возвращаемые этими функциями, являются гибкими (см. статью " Гибкие объекты" в C++/WinRT). Это просто то, что объекты являются однопоточными. И это вполне уместно, если вы просто хотите передавать данные в ту или иную сторону через интерфейс двоичной совместимости приложений (ABI).

Базовые классы для коллекций

Если для полной гибкости вы хотите реализовать собственную настраиваемую коллекцию, то вам потребуется избежать этого сложного способа. Например, вот как выглядело бы пользовательское представление вектора без помощи базовых классов C++/WinRT.

...
using namespace winrt;
using namespace Windows::Foundation::Collections;
...
struct MyVectorView :
    implements<MyVectorView, IVectorView<float>, IIterable<float>>
{
    // IVectorView
    float GetAt(uint32_t const) { ... };
    uint32_t GetMany(uint32_t, winrt::array_view<float>) const { ... };
    bool IndexOf(float, uint32_t&) { ... };
    uint32_t Size() { ... };

    // IIterable
    IIterator<float> First() const { ... };
};
...
IVectorView<float> view{ winrt::make<MyVectorView>() };

Вместо этого гораздо проще унаследовать собственное представление вектора от шаблона структуры winrt::vector_view_base и просто реализовать функцию get_container, чтобы предоставить доступ к контейнеру, хранящему ваши данные.

struct MyVectorView2 :
    implements<MyVectorView2, IVectorView<float>, IIterable<float>>,
    winrt::vector_view_base<MyVectorView2, float>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

private:
    std::vector<float> m_values{ 0.1f, 0.2f, 0.3f };
};

Контейнер, возвращаемый get_container , должен предоставить начальный и конечный интерфейс, который ожидает winrt::vector_view_base . Как показано в приведенном выше примере, std::vector предоставляет это. Но вы можете вернуть любой контейнер, выполняющий тот же контракт, включая собственный пользовательский контейнер.

struct MyVectorView3 :
    implements<MyVectorView3, IVectorView<float>, IIterable<float>>,
    winrt::vector_view_base<MyVectorView3, float>
{
    auto get_container() const noexcept
    {
        struct container
        {
            float const* const first;
            float const* const last;

            auto begin() const noexcept
            {
                return first;
            }

            auto end() const noexcept
            {
                return last;
            }
        };

        return container{ m_values.data(), m_values.data() + m_values.size() };
    }

private:
    std::array<float, 3> m_values{ 0.2f, 0.3f, 0.4f };
};

Это базовые классы, которые предоставляет C++/WinRT для реализации пользовательских коллекций.

winrt::vector_view_base

См. приведенные выше примеры кода.

winrt::vector_base

struct MyVector :
    implements<MyVector, IVector<float>, IVectorView<float>, IIterable<float>>,
    winrt::vector_base<MyVector, float>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::vector<float> m_values{ 0.1f, 0.2f, 0.3f };
};

winrt::observable_vector_base

struct MyObservableVector :
    implements<MyObservableVector, IObservableVector<float>, IVector<float>, IVectorView<float>, IIterable<float>>,
    winrt::observable_vector_base<MyObservableVector, float>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::vector<float> m_values{ 0.1f, 0.2f, 0.3f };
};

winrt::map_view_base

struct MyMapView :
    implements<MyMapView, IMapView<winrt::hstring, int>, IIterable<IKeyValuePair<winrt::hstring, int>>>,
    winrt::map_view_base<MyMapView, winrt::hstring, int>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

private:
    std::map<winrt::hstring, int> m_values{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    };
};

winrt::map_base

struct MyMap :
    implements<MyMap, IMap<winrt::hstring, int>, IMapView<winrt::hstring, int>, IIterable<IKeyValuePair<winrt::hstring, int>>>,
    winrt::map_base<MyMap, winrt::hstring, int>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::map<winrt::hstring, int> m_values{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    };
};

winrt::observable_map_base

struct MyObservableMap :
    implements<MyObservableMap, IObservableMap<winrt::hstring, int>, IMap<winrt::hstring, int>, IMapView<winrt::hstring, int>, IIterable<IKeyValuePair<winrt::hstring, int>>>,
    winrt::observable_map_base<MyObservableMap, winrt::hstring, int>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::map<winrt::hstring, int> m_values{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    };
};

Важные API