TL; DR Из-за отрицательных голосов и отсутствия ответов и комментариев я предполагаю, что TL; DR необходим.
Как я могу создать структуру в golang, содержащую указатель, а затем безопасно передать ее по значению другим функциям? (Под безопасным я подразумеваю, не беспокоясь о том, что эти функции могут разыменовывать указанный указатель и изменять переменную, на которую он указывает).
А ТАКЖЕ
Если вы собираетесь дать ответ «Функции копирования», то как я могу удалить исходный конструктор/оператор копирования? Переопределить его с помощью пользовательской функции копирования? Или иным образом отговорить людей от его использования?
В golang у меня может быть структура, содержащая указатель на динамически выделяемую переменную.
Я также могу передавать экземпляры этих структур функциям, которые их «копируют».
Однако я не могу переопределить или удалить встроенный оператор копирования. Это означает, что теоретически у меня мог бы быть такой код:
import (
"fmt"
)
type A struct {
a * int
}
func main() {
var instance A
value := 14
instance.a = &value
fmt.Println(*instance.a) // prints 14
mutator(instance)
fmt.Println(*instance.a) // prints 11 o.o
}
func mutator(instance A) {
*instance.a = 11
fmt.Println(*instance.a)
}
Этот тип кода, очевидно, немного бессмысленен здесь. Однако, предполагая, что поле-член "a" представляет собой сложную структуру, вполне возможно, что функция, обращающаяся к нему, попытается изменить его.
Вполне разумно также, что после вызова функции «мутатор» программист может захотеть продолжить использование своего экземпляра A и (предполагая, что он не обязательно кодировал структуру или знал о ее внутренностях) мог даже предположить, что, поскольку он передал копию, а не указатель, его экземпляр A останется неизменным.
Теперь есть несколько (3) популярных языков, которые позволяют программисту думать о выделении и управлении памятью, которые не являются golang. Я не знаю ни Rust, ни C, поэтому воздержусь от того, как бы я решил эту проблему на C++:
а) Предполагая, что я был разработчиком класса A, я мог бы создать конструктор копирования, что привело бы к следующему коду:
#include <iostream>
class A {
public:
int * a;
A(int value): a(new int{value}) {}
A(const A & copyFrom): a(new int{*copyFrom.a}) {}
};
void mutator(A instance) {
*instance.a = 11;
std::cout << *instance.a << "\n";
}
int main() {
A instance{14};
std::cout << *(instance.a) << "\n";
mutator(instance);
std::cout << *instance.a << "\n";
}
Это позволяет копировать экземпляр моего класса с добавленной оговоркой, что указатель также переназначается.
б) Предполагая, что я был разработчиком класса A и не хотел создавать конструктор копирования (скажем, что бы ни указывало на, может быть очень большим или что A часто используется в критических условиях производительности как объект только для чтения), но хотел убедитесь, что любое назначение для копирования не может изменить значение, на которое указывает a (но все же разрешить людям изменять a, присваивая ему новое значение). Я мог бы написать свой класс следующим образом:
class A {
public:
const int * a;
A(int value): a(new const int{value}) {}
};
Что приведет к тому, что следующий код не сможет скомпилироваться:
void mutator(A instance) {
*instance.a = 11;
std::cout << *instance.a << "\n";
}
int main() {
A instance{14};
std::cout << *(instance.a) << "\n";
mutator(instance);
std::cout << *instance.a << "\n";
}
Но следующий код скомпилируется просто отлично:
void mutator(A instance) {
instance.a = new const int{11};
std::cout << *instance.a << "\n";
}
int main() {
A instance{14};
std::cout << *(instance.a) << "\n";
mutator(instance);
std::cout << *instance.a << "\n";
}
Имейте в виду, что это типично для "объектно-ориентированного" дизайна C++ (э-э-э). Я бы предпочел, чтобы в сигнатуре функции было какое-то правило, которое гарантировало бы отсутствие модификации экземпляра A, переданного ей, или метод, с помощью которого можно объявить экземпляр A "константным" и "защитить" его динамически. выделенные поля (не только статические) от повторного назначения.
Однако, хотя решение может быть не идеальным, это решение. Это позволяет мне иметь четкое представление о «владении» моими экземплярами A.
В golang кажется, что любая «копия» экземпляра, содержащая указатели, в основном бесплатна для всех, ее нельзя безопасно передавать, даже если у автора структуры было такое намерение.
Единственное, о чем я могу думать, это иметь метод «Копировать», который возвращает совершенно новый экземпляр структуры (аналогичный конструктору копирования в приведенном выше примере). Но без возможности удалить конструктор/оператор копирования было бы трудно убедиться, что люди будут его использовать и/или заметят.
Честно говоря, мне кажется довольно странным, что golang даже позволяет перезаписать адрес памяти указателя без использования «небезопасного» пакета или чего-то подобного.
Не было бы разумнее просто запретить этот тип операций, как и многие другие?
Учитывая то, как работает «добавление», кажется совершенно очевидным, что намерение авторов состоит в том, чтобы способствовать повторному назначению новых переменных указателю, а не изменению той, на которую он ранее указывал. Однако, хотя это легко реализовать с помощью встроенной структуры, такой как срез или массив, довольно сложно реализовать с помощью пользовательской структуры (по крайней мере, без упаковки указанной структуры в пакет).
Я упускаю из виду способ создания копии (или запрета копирования) в golang? Действительно ли первоначальное намерение авторов заключалось в поощрении переназначения, а не мутации, когда это позволяют память и время? Если да, то почему так легко изменять динамически выделяемые переменные? Есть ли способ имитировать поведение private/public со структурами или файлами, а не с полноценными пакетами? Есть ли другой способ обеспечить некоторое подобие владения структурами, имеющими указатели, которые я упускаю из виду?