Структура не передается по ссылке при передаче методу

#struct #vala

#struct #vala

Вопрос:

 struct Data {
    public int x;
}

void change_x(Data data) {
    data.x = 123;
}

Data a = Data();
change_x(a);
print("%d", a.x); // 0
  

но в документе говорится:

когда экземпляр типа struct передается методу, копия не создается. Вместо этого передается ссылка на экземпляр.
— в https://wiki.gnome.org/Projects/Vala/Manual/Types

Что не так?

Ответ №1:

Структуры в Vala реализуются как копирование при присвоении и передача по ссылке. Таким образом, вы можете рассматривать свой пример как копирование структуры, потому что она присваивается параметру в функции, а затем эта копия передается по ссылке. Это то, что происходит за кулисами в сгенерированном коде C, но со стороны Vala это означает, что struct является типом значения. Полезно знать, что копия структуры передается по ссылке только при взаимодействии с библиотекой C. Цитата из руководства относится к методам struct, но прежде чем мы рассмотрим это подробно, давайте немного больше разберемся в типах значений и ссылочных.

Vala, как Java, C # и многие другие языки, имеет два вида типов данных: типы значений и ссылочные типы.

Типы значений передаются по значению

Когда тип значения передается в качестве аргумента функции или метода, тогда значение передается в качестве аргумента, но это копия значения. Если функция или метод продолжит изменять полученный параметр, это не изменит значение в вызывающем коде. Код инкапсулирован.

Следующий пример:

 void main () {
        int a = 23;
        print ("Initial value: %in", a);
        modify_example (a);
        print ("Final value: %in", a);
}

void modify_example (int x) {
        x  = 100;
}
  

выдает:

 Initial value: 23
Final value: 23
  

Хотя значение изменяется в функции, оно также не изменяет значение из вызывающего кода.

Типы значений могут передаваться по ссылке

Вместо передачи значения функции или методу использование ref ключевого слова передаст ссылку на значение. Это создает псевдоним для вызывающего значения. Результатом являются два идентификатора для одной и той же ячейки памяти.

Просто добавив ref ключевое слово в следующем примере:

 void main () {
        int a = 23;
        print ("Initial value: %in", a);
        modify_example (ref a);
        print ("Final value: %in", a);
}

void modify_example (ref int x) {
        x  = 100;
}
  

теперь выдает:

 Initial value: 23
Final value: 123
  

При вызове modify_example () побочным эффектом также является изменение значения в вызывающем коде. Использование ref делает это явным и может использоваться как способ для функции возвращать несколько значений, но в этом примере было бы понятнее return использовать измененное значение вместо передачи по ссылке.

Ссылочные типы всегда передаются по ссылке

Объекты являются ссылочными типами. В этом примере не используется явное ref , но значение изменено в вызывающем коде:

 void main () {
        var a = new ExampleReferenceType (23);
        print ("Initial value: %in", a.value);
        modify_example (a);
        print ("Final value: %in", a.value);
}

class ExampleReferenceType {
        public int value;

        public ExampleReferenceType (int default = 0) {
                this.value = defau<
        }
}

void modify_example (ExampleReferenceType x) {
        x.value  = 100;
}
  

Это приводит к:

 Initial value: 23
Final value: 123
  

Объекты, измененные таким образом, могут вызвать проблемы при отслеживании ошибки. В этом заключается преимущество создания объектов значений неизменяемыми. Это было бы сделано только путем установки значения в конструкторе, делая все поля закрытыми и используя свойство только для получения значения, но не для его установки.

Структуры как типы значений

Следующий код:

 void main () {
        ExampleStruct a = { 23 };
        print ("Initial value: %in", a.value);
        modify_example (a);
        print ("Final value: %in", a.value);
}

private struct ExampleStruct {
        public int value;
}

void modify_example (ExampleStruct x) {
        x.value  = 100;
}
  

аналогичен вашему коду и выдает:

 Initial value: 23
Final value: 23
  

Это такое же поведение в Vala, как и для других типов значений, но если вы посмотрите на код C с помощью --ccode переключателя с valac , вы увидите, что структура скопирована и передана по ссылке. Это актуально, когда вам нужно понять, как Vala поддерживает C ABI (двоичный интерфейс приложения).

Методы структуры

Ссылка, которую вы делаете на руководство, может быть непонятной, но я думаю, что это относится к методам внутри структур. If modify_example перемещается внутри определения структуры:

 void main () {
        ExampleStruct a = { 23 };
        print ("Initial value: %in", a.value);
        a.modify_example ();
        print ("Final value: %in", a.value);
}

private struct ExampleStruct {
        public int value;

        public void modify_example () {
                this.value  = 100;
        }
}
  

теперь это приводит к:

 Initial value: 23
Final value: 123
  

Теперь метод работает с экземпляром.

Структуры [simpleType]

В разделе из руководства, которое вы цитируете, также указано в следующем предложении:

Это поведение можно изменить, объявив структуру как простой тип.

Итак, для полноты картины приведем последний пример:

 void main () {
        ExampleStruct a = { 23 };
        print ("Initial value: %in", a.value);
        a.modify_example ();
        print ("Final value: %in", a.value);
}

[SimpleType]
private struct ExampleStruct {
        public int value;

        public void modify_example () {
                this.value  = 100;
        }
}
  

и это приводит к:

 Initial value: 23
Final value: 23
  

Хотя метод все еще определен внутри структуры, экземпляр передается по значению вместо ссылки.

Структуры простого типа — это то, как определяются основные типы значений в Vala, например, int и int64 . Если вы хотите, чтобы был определен метод struct, который действует на экземпляр простого типа struct, тогда метод должен быть определен для возврата нового экземпляра, содержащего измененное значение.

Заключение

Структуры — это типы значений в Vala, но полезно знать, как они реализованы, чтобы понять совместимость с C ABI. В вашем примере структура ведет себя как тип значения, но копируется и передается по ссылке в терминах C ABI. Цитата кажется наиболее подходящей для методов, определенных в структурах.

Ответ №2:

Я думаю, что текст в кавычках, на который вы ссылаетесь, либо устарел, либо изначально был неправильным.

Вы должны использовать ref (или out ), если хотите, чтобы она передавалась по ссылке (отсюда и название ref ).

 struct Data {
    public int x;
}

void change_x (ref Data data) {
    data.x = 123;
}

int main () {
    Data a = Data ();
    change_x (ref a);
    print ("%dn", a.x);
    return 0;
}
  

Комментарии:

1. Если вы посмотрите на код C, текст руководства правильный. Сигнатура функции для выходного C является void data_change_x (Data* data) . Однако основная функция использует временную переменную для вызова функции, и я не уверен, почему это так. Код также работает, если change_x сделан общедоступным методом структуры.

2. Хороший момент, я не смотрел на вывод C. Компилятор Vala обычно использует много временных переменных. Вывод все тот же: если вы хотите передать что-то по ссылке в Vala, всегда используйте ref . Для этого и предназначено ключевое слово.

3. За исключением классов, конечно, которые всегда передаются как ссылки AFAIK.