Golang 复制包含指针的结构

Golang copying structures that contain pointers

长话短说;博士 由于反对票和缺乏答案以及评论,我认为需要 tl;dr。

如何在 golang 中创建一个包含指针的结构,然后安全地将其按值传递给其他函数? (我所说的安全是指不必担心这些函数可以取消引用所述指针并更改其指向的变量)。

如果你要给出的答案是"Copy functions"那我怎样才能删除原来的副本constructor/operator呢?用我的自定义复制功能覆盖它?或者以其他方式阻止人们使用它?


在 golang 中,我可以有一个包含指向动态分配变量的指针的结构。

我还可以将这些结构的实例传递给 "copy" 它们的函数。

但是,我无法覆盖或删除内置复制运算符。这意味着,理论上,我可以编写如下代码:

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) 假设我是 class 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";
    }

这允许复制我的 class 的实例,但要注意指针也被重新分配。

b) 假设我是 class A 的设计者并且不想构建复制构造函数(假设 a 指向的任何东西都可能非常大或者 A 经常用于性能关键条件作为只读对象)但又想确保任何要复制的赋值都不能修改 a 指向的值(但仍然允许人们通过将 a 分配给新值来修改 a )我可以写我的 class 像这样:

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++ "Object Oriented" (eegh) 设计。如果我可以在函数签名中有某种规则来保证不修改传递给它的 A 实例或声明 A "const" 和 [=68 的实例的方法,我会更喜欢=] 其动态分配的字段(不仅是静态字段)反对重新分配。

然而,虽然解决方案可能并不完美,但它是一个解决方案。它让我对我的 A.

实例的 "ownership" 有一个清晰的认识

在 golang 中,似乎任何 "copy" 包含指针的实例基本上都是免费的,即使结构的作者有这样的意图,它也不能安全地传递。

我能想到的一件事是有一个 "Copy" 方法 return 一个全新的结构实例(类似于上面示例中的复制构造函数)。但是如果没有删除副本的能力 constructor/operator 就很难确保人们会使用 and/or 注意到它。

老实说,golang 甚至允许在不使用 "unsafe" 包或类似的东西的情况下重写指针的内存地址,这对我来说似乎很奇怪。

像许多其他操作一样简单地禁止此类操作是否更有意义?

考虑到 "append" 的工作方式,作者的意图似乎很明显是支持将新变量重新分配给指针,而不是改变它之前指向的变量。然而,虽然这很容易用像切片或数组这样的内置结构来实施,但似乎很难用自定义结构来实施(至少没有将所述结构包装在一个包中)。

我是否忽略了在 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++。

"copy constructor/operator" 和 "Override it with custom function" 是 C++ 概念。

参考文献:

The Go Programming Language Specification

Blocks

Declarations and scope

Exported identifiers