TL; 博士
由于投票不足,缺乏答案和评论,我认为需要 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”是一个复杂的结构,访问它的函数可能会尝试修改它。
这也可能是合乎情理的,一旦函数“mutator”被调用,程序员可能想要继续使用他的 A 实例,并且(假设他不一定对结构进行编码或了解其内部结构)甚至可能假设因为他传递了一个副本而不是一个指针,他的 A 实例将保持不变。
现在,有几 (3) 种流行的语言允许程序员考虑分配和操作不是 golang 的内存。我不知道 Rust 或 C,所以我将避免在 C++ 中如何解决这个问题:
a) 假设我是 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";
}
这允许复制我的类的一个实例,并添加一个警告,即指针也被重新分配。
b) 假设我是 A 类的设计者并且不想构建复制构造函数(比如 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++ 的“面向对象”(eegh)设计的典型特征。如果我可以在函数签名中有某种规则来保证不会修改传递给它的 A 的实例或一种方法来动态声明 A 的实例“const”并“保护”它,我会更喜欢分配的字段(不仅是静态的)防止重新分配。
然而,虽然解决方案可能并不完美,但它是一个解决方案。它让我对我的 A 实例的“所有权”有一个清晰的认识。
在 golang 中,似乎包含指针的实例的任何“副本”基本上对所有人都是免费的,即使结构的作者有这样的意图,也不能安全地传递它。
我能想到的一件事是有一个“复制”方法,它返回一个全新的结构实例(类似于上面例子中的复制构造函数)。但是如果没有删除复制构造函数/运算符的能力,就很难确保人们会使用和/或注意到它。
老实说,golang 甚至允许在不使用“不安全”包或类似的东西的情况下重写指针的内存地址,这对我来说似乎很奇怪。
像许多其他操作一样简单地禁止此类操作不是更有意义吗?
考虑到“追加”的工作方式,作者的意图似乎很明显是倾向于将新变量重新分配给一个指针,而不是改变它之前指向的变量。然而,虽然这很容易通过像 slice 或数组这样的内置结构来实现,但似乎很难用自定义结构来实现(至少没有将所述结构包装在一个包中)。
我是否忽略了在 golang 中进行复制构造(或禁止复制)的方法?在内存和时间允许的情况下,鼓励重新分配而不是突变真的是作者的初衷吗?如果是这样,为什么改变动态分配的变量如此容易?有没有办法用结构或文件而不是完整的包来模拟私有(private)/公共(public)行为?有没有其他方法可以通过具有我忽略的指针的结构来强制执行某种所有权的外观?
最佳答案
How can I create a struct in golang, which contains a pointer, then safely pass it by value to other functions ? (By safely I mean without having to worry that those functions can dereference said pointer and change the variable its pointing to).
使用带有未导出字段的导出包类型。例如,
src/ptrstruct/ptrstruct.go
:package ptrstruct
type PtrStruct struct {
pn *int
}
func New(n int) *PtrStruct {
return &PtrStruct{pn: &n}
}
func (s *PtrStruct) N() int {
return *s.pn
}
func (s *PtrStruct) SetN(n int) {
*s.pn = n
}
func (s *PtrStruct) Clone() *PtrStruct {
// make a deep clone
t := &PtrStruct{pn: new(int)}
*t.pn = *s.pn
return t
}
src/ptrstruct.go
:package main
import (
"fmt"
"ptrstruct"
)
func main() {
ps := ptrstruct.New(42)
fmt.Println(ps.N())
pc := ps.Clone()
fmt.Println(pc.N())
pc.SetN(7)
fmt.Println(pc.N())
fmt.Println(ps.N())
}
输出:
src $ go run ptrstruct.go
42
42
7
42
src $
If the answer you are going to give is "Copy functions" then how can I delete the original copy constructor/operator ? Override it with me custom copy function ? Or otherwise discourage people from using it ?
停止用 C++ 编程;开始在 Go 中编程。按照设计,Go 不是 C++。
“复制构造函数/运算符”和“用自定义函数覆盖它”是 C++ 概念。
引用:
The Go Programming Language Specification
Blocks
Declarations and scope
Exported identifiers
关于pointers - Golang 复制包含指针的结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43622388/