Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Как объясняется в Создание API с помощью C++/WinRT, при создании объекта типа реализации следует использовать семейство вспомогательных функций winrt::make. В этом разделе подробно описана функция C++/WinRT 2.0, которая помогает диагностировать ошибку прямого выделения объекта типа реализации в стеке.
Такие ошибки могут превратиться в таинственные аварии или повреждения, которые являются трудными и трудоемкими для отладки. Итак, это важная особенность, и стоит понять предысторию.
Настройка сцены с помощью MyStringable
Сначала рассмотрим простую реализацию IStringable.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const { return L"MyStringable"; }
};
Теперь представьте, что вам нужно вызвать функцию (внутри вашей реализации), которая ожидает IStringable в качестве аргумента.
void Print(IStringable const& stringable)
{
printf("%ls\n", stringable.ToString().c_str());
}
Проблема заключается в том, что наш тип MyStringableне является IStringable.
- Наш тип MyStringable — это реализация интерфейса IStringable .
- Тип IStringable — это проецируемый тип.
Это важно
Важно понимать различие между типом реализации и проецируемым типом. Чтобы ознакомиться с основными понятиями и терминами, обязательно прочитайте статьи Использование API с помощью C++/WinRT и Создание API с помощью C++/WinRT.
Пространство между реализацией и проекцией может быть тонким для понимания. И действительно, чтобы реализация в большей степени напоминала проекцию, в ней предусмотрены неявные преобразования к каждому из проецируемых типов, которые она реализует. Это не означает, что мы можем просто сделать это.
struct MyStringable : implements<MyStringable, IStringable>
{
winrt::hstring ToString() const;
void Call()
{
Print(this);
}
};
Вместо этого необходимо получить ссылку, чтобы операторы преобразования могли использоваться в качестве кандидатов для разрешения вызова.
void Call()
{
Print(*this);
}
Это работает. Неявное преобразование обеспечивает (очень эффективное) преобразование из типа реализации в проецируемый тип, и это очень удобно для многих сценариев. Без этого объекта многие типы реализаций могут оказаться очень громоздкими для автора. При условии, что для выделения памяти под реализацию вы используете только шаблон функции winrt::make (или winrt::make_self), тогда всё в порядке.
IStringable stringable{ winrt::make<MyStringable>() };
Потенциальные ловушки с C++/WinRT 1.0
Тем не менее неявные преобразования могут привести к проблемам. Рассмотрим эту бесполезную вспомогательную функцию.
IStringable MakeStringable()
{
return MyStringable(); // Incorrect.
}
Или даже просто это, по-видимому, безвредное заявление.
IStringable stringable{ MyStringable() }; // Also incorrect.
К сожалению, такой код компилировался с помощью C++/WinRT 1.0 из-за этого неявного преобразования. Проблема (и очень серьёзная) в том, что мы потенциально возвращаем спроецированный тип, указывающий на объект с подсчётом ссылок, чья базовая область памяти находится во временном стеке.
Вот что-то другое, скомпилированное с помощью C++/WinRT 1.0.
MyStringable* stringable{ new MyStringable() }; // Very inadvisable.
Необработанные указатели являются опасными и трудоемкими источниками ошибок. Не используйте их, если вам не нужно. C++/WinRT делает всё возможное, чтобы обеспечить максимальную эффективность, не вынуждая вас использовать сырые указатели. Вот что-то другое, скомпилированное с помощью C++/WinRT 1.0.
auto stringable{ std::make_shared<MyStringable>(); } // Also very inadvisable.
Это ошибка на нескольких уровнях. У нас два разных счетчика ссылок для одного объекта. среда выполнения Windows (и классический COM перед ним) основан на встроенном счетчике ссылок, несовместимом с std::shared_ptr. std::shared_ptr имеет, конечно, много допустимых приложений; но это совершенно не требуется, когда вы предоставляете общий доступ к объектам среда выполнения Windows (и классическим COM). Наконец, это также скомпилировано с помощью C++/WinRT 1.0.
auto stringable{ std::make_unique<MyStringable>() }; // Highly dubious.
Это снова довольно сомнительным. Уникальное владение противопоставляется разделяемому времени жизни встроенного в MyStringable счетчика ссылок.
Решение с помощью C++/WinRT 2.0
При использовании C++/WinRT 2.0 все эти попытки напрямую выделить типы реализаций приводят к ошибке компилятора. Это лучший вид ошибки, и бесконечно лучше, чем загадочная ошибка среды выполнения.
Всякий раз, когда необходимо сделать реализацию, можно просто использовать winrt::make или winrt::make_self, как показано выше. И теперь, если вы забудете это сделать, то получите ошибку компиляции, которая будет на это намекать, ссылаясь на абстрактный метод с именем use_make_function_to_create_this_object. Это не совсем static_assert, но это близко. Тем не менее, это самый надежный способ обнаружения всех описанных ошибок.
Это означает, что нам нужно поместить несколько незначительных ограничений на реализацию. Поскольку мы полагаемся на отсутствие переопределения, чтобы обнаружить прямое выделение памяти, шаблон функции winrt::make должен каким-то образом предоставить переопределение для абстрактной виртуальной функции. Это делается путем извлечения из реализации с классом final , предоставляющим переопределение. Следует отметить несколько моментов, касающихся этого процесса.
Во-первых, виртуальная функция присутствует только в отладочных сборках. Это означает, что обнаружение не будет влиять на размер vtable в оптимизированных сборках.
Во-вторых, так как производный класс , который winrt::make использует, finalозначает, что любая девиртуализация, которую оптимизатор может вывести, может произойти, даже если вы ранее не решили пометить класс реализации как final. Так что это улучшение. И наоборот: реализация не может быть final. Опять же, это не имеет значения, так как созданный тип всегда будет final.
В-третьих, ничего не мешает пометить какие-либо виртуальные функции в реализации как final. Конечно, C++/WinRT очень отличается от классического COM и таких реализаций, как WRL, где почти всё в вашей реализации, как правило, виртуально. В C++/WinRT виртуальная диспетчеризация ограничена двоичным интерфейсом приложения ( finalABI), а методы реализации зависят от времени компиляции или статического полиморфизма. Это позволяет избежать ненужного полиморфизма среды выполнения, а также означает, что в реализации C++/WinRT нет драгоценных причин для виртуальных функций. Это очень хорошо и приводит к гораздо более предсказуемому инлайнингу.
В-четвертых, так как winrt::make внедряет производный класс, реализация не может иметь частный деструктор. Частные деструкторы были популярны в классических реализациях COM, потому что, опять же, всё было виртуальным, и часто работали напрямую с сырыми указателями, поэтому было легко случайно вызвать delete вместо Release. C++/WinRT всячески старается затруднить вам прямую работу с сырыми указателями. И вам пришлось бы действительно очень постараться, чтобы получить необработанный указатель в C++/WinRT, для которого вы потенциально могли бы вызвать delete. Семантика значений означает, что вы работаете со значениями и ссылками; и редко с указателями.
Таким образом, C++/WinRT оспаривает наши предубеждительные понятия о том, что означает написание классического COM-кода. И это совершенно разумно, потому что WinRT не является классическим COM. Классический COM — это язык сборки среда выполнения Windows. Это не должен быть код, который вы пишете каждый день. Вместо этого C++/WinRT позволяет писать код, который больше похож на современный C++, и гораздо менее похож на классический COM.
Важные API
Связанные темы
Windows developer