Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Как описано в 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 виртуальная диспетчеризация ограничена двоичным интерфейсом приложения (ABI) (который всегда final
), а методы реализации зависят от времени компиляции или статического полиморфизма. Это позволяет избежать ненужного полиморфизма среды выполнения, а также означает, что в реализации C++/WinRT нет драгоценных причин для виртуальных функций. Это очень хороший факт, что приводит к гораздо более предсказуемому встраиванию.
В-четвертых, так как winrt::make внедряет производный класс, реализация не может иметь частный деструктор. Частные деструкторы были популярны в классических реализациях COM, потому что, опять же, все было виртуальным, и было обычно работать непосредственно с необработанными указателями, поэтому легко было случайно вызвать delete
вместо Release. C++/WinRT прилагает усилия, чтобы усложнить вам работу с сырыми указателями. И вам придется действительно очень постараться, чтобы получить необработанный указатель в C++/WinRT, на который вы потенциально могли бы вызвать delete
. Семантика значений означает, что вы работаете со значениями и ссылками; и редко с указателями.
Таким образом, C++/WinRT оспаривает наши предубеждительные понятия о том, что означает написание классического COM-кода. И это совершенно разумно, потому что WinRT не является классическим COM. Классический COM — это язык сборки среды выполнения Windows. Это не должен быть код, который вы пишете каждый день. Вместо этого C++/WinRT позволяет писать код, который больше похож на современный C++, и гораздо менее похож на классический COM.