Кто-то может объяснить меня следующий фрагмент кода?
value struct ValueStruct {
int x;
};
void SetValueOne(ValueStruct% ref) {
ref.x = 1;
}
void SetValueTwo(ValueStruct ref) {
ref.x = 2;
}
void SetValueThree(ValueStruct^ ref) {
ref->x = 3;
}
ValueStruct^ first = gcnew ValueStruct;
first->x = 0;
SetValueOne(*first);
ValueStruct second;
second.x = 0;
SetValueTwo(second); // am I creating a copy or what? is this copy Disposable even though value types don't have destructors?
ValueStruct^ third = gcnew ValueStruct;
third->x = 0;
SetValueThree(third); // same as the first ?
И мой второй вопрос: там какая-либо причина состоит в том, чтобы иметь что-то как этот?:
ref struct RefStruct {
int x;
};
RefStruct% ref = *gcnew RefStruct;
// rather than:
// RefStruct^ ref = gcnew RefStruct;
// can I retrieve my handle from ref?
// RefStruct^ myref = ???
Что больше: Я не вижу различия между типом значения и касательно типа, так как на обоих может указать обработчик ;(
Помните, что основное использование C++/CLI - это разработка библиотек классов для использования в графических интерфейсах / веб-сервисах, созданных на других языках .NET. Поэтому C++/CLI должен поддерживать как ссылочные типы, так и типы значений, поскольку другие языки .NET это делают.
Более того, в C# могут быть ref
параметры, которые также являются типами значений, это не уникально для C++/CLI и никоим образом не делает типы значений эквивалентными ссылочным типам.
Чтобы ответить на вопросы в комментариях к вашему коду:
я создаю копию или что?
Да, SetValueTwo принимает свой параметр по значению, поэтому создается копия.
Является ли эта копия одноразовой, несмотря на то, что у типов значений нет деструкторов?
Неверно. Типы значений могут иметь деструкторы. У типов значений не может быть финализаторов. Поскольку этот конкретный тип значения имеет тривиальный деструктор, компилятор C++/CLI не заставит его реализовать IDisposable. В любом случае, если параметр является типом значения IDisposable, компилятор C++/CLI обеспечит вызов Dispose, когда переменная выходит из области видимости, подобно семантике стека для локальных переменных. Это включает аномальное завершение (выброшенное исключение) и позволяет использовать управляемые типы с RAII.
Разрешены оба варианта
ValueStruct% ref = *gcnew ValueStruct;
и
ValueStruct^ ref = gcnew ValueStruct;
и помещают экземпляр типа boxed value на управляемую кучу (которая вообще не является кучей, а очередью FIFO, однако Microsoft предпочитает называть ее кучей, как родную область памяти для динамического распределения).
В отличие от C#, C++/CLI может хранить типизированные дескрипторы для коробочных объектов.
Если отслеживающая ссылка относится к экземпляру типа значения на стеке или встроена в другой объект, то содержимое типа значения должно быть отсечено в процессе формирования ссылки.
Отслеживающие ссылки могут также использоваться с ссылочными типами, и синтаксис для получения дескриптора такой же:
RefClass^ newinst = gcnew RefClass();
RefClass% reftoinst = *newinst;
RefClass^% reftohandle = newinst;
RefClass stacksem;
RefClass^ ssh = %stacksem;
Одна вещь, которую я никогда не могу запомнить полностью, заключается в том, что синтаксис не является на 100% последовательным по сравнению с родным C++.
Объявить ссылку:
int& ri = i; // native
DateTime% dtr = dt; // managed tracking reference
Объявить указатель:
int* pi; // native
Stream^ sh; // tracking handle
Сформировать указатель:
int* pi = &ri; // address-of native object
DateTime^ dth = %dtr; // address-of managed object
Обратите внимание, что унарный оператор address-of совпадает с обозначением ссылки как в стандартном C++, так и в C++/CLI. Это, кажется, противоречит отслеживающая ссылка не может быть использована как унарный оператор take-address (MSDN), к которому я вернусь через секунду.
Сначала, однако, о несоответствии:
Формируем ссылку из указателя:
int& iref = *pi;
DateTime% dtref = *dth;
Обратите внимание, что унарный оператор разыменования всегда *
. Он совпадает с обозначением указателя только в родном мире, что полностью противоположно адресам, которые, как уже говорилось выше, всегда совпадают с обозначением ссылки.
Компилируемый пример:
DateTime^ dth = gcnew DateTime();
DateTime% dtr = *dth;
DateTime dt = DateTime::Now;
DateTime^ dtbox = %dt;
FileInfo fi("temp.txt");
// FileInfo^ fih = &fi; causes error C3072
FileInfo^ fih = %fi;
Теперь об унарном address-of:
Во-первых, статья MSDN неверна, когда говорит:
Следующий пример показывает, что отслеживающая ссылка не может быть использована в качестве унарного оператора take-address.
Правильное утверждение таково:
%
является оператором адреса для создания дескриптора отслеживания. Однако его использование ограничено следующим образом:
Дескриптор отслеживания должен указывать на объект в управляемой куче. Ссылочные типы всегда существуют на управляемой куче, поэтому проблем не возникает. Однако типы значений и собственные типы могут находиться на стеке (для локальных переменных) или быть встроенными в другой объект (переменные-члены типа значения). При попытке сформировать отслеживающий дескриптор будет сформирован дескриптор боксированной копии переменной: дескриптор не связан с исходной переменной. Вследствие процесса боксирования, который требует метаданных, не существующих для собственных типов, никогда невозможно иметь отслеживающий дескриптор к экземпляру собственного типа.
Пример кода:
int i = 5;
// int^ ih = %i; causes error C3071
System::Int32 si = 5;
// System::Int32^ sih = %si; causes error C3071
// error C3071: operator '%' can only be applied to an instance
// of a ref class or a value-type
Если System::Int32
не является типом значений, тогда я не знаю, что является. Давайте попробуем System::DateTime
, который является непримитивным типом значения:
DateTime dt = DateTime::Now;
DateTime^ dtbox = %dt;
Это работает!
Как еще одно досадное ограничение, примитивные типы, имеющие двойную идентичность (например, родной int
и управляемый тип значения System::Int32
), обрабатываются неправильно, оператор %
(отслеживание формы ссылки) не может выполнить боксирование даже когда указано .NET-имя типа.