【问题标题】:Golang copying structures that contain pointers包含指针的 Golang 复制结构
【发布时间】:2017-04-25 23:07:08
【问题描述】:

TL;DR 由于不赞成票和缺乏答案以及我认为需要 tl;dr 的 cmets。

如何在 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 甚至允许重写指针的内存地址而不使用“不安全”包或类似的东西,这对我来说似乎很奇怪。

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

考虑到“附加”的工作方式,作者的意图似乎很明显是倾向于将新变量重新分配给指针,而不是改变它之前指向的变量。然而,虽然这很容易使用像切片或数组这样的内置结构来强制执行,但使用自定义结构似乎很难强制执行(至少没有将所述结构包装在包中)。

我是否忽略了在 golang 中进行复制构造(或禁止复制)的方法?在记忆和时间允许的情况下,作者的初衷是鼓励重新分配而不是突变吗?如果是这样,为什么改变动态分配的变量如此容易?有没有办法用结构或文件而不是完整的包来模拟私有/公共行为?有没有其他方法可以使用具有我忽略的指针的结构来强制实现所有权的外观?

【问题讨论】:

  • 请编辑问题以将其限制为具有足够详细信息的特定问题,以确定适当的答案。避免同时提出多个不同的问题。
  • 在 Go 中,这非常简单:使用您的包的人无法访问未导出的标识符。也就是说,从 C++ 的角度来看,它们实际上是私有的。如果您提供了一种访问结构中指针的方法(例如,返回此类指针的方法),那么您要么复制其数据并返回指向副本的指针,要么只是相信您的包的用户不会破坏事物.双方都有争论,但最终取决于您的用例。

标签: pointers memory go ownership


【解决方案1】:

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

使用带有未导出字段的导出包类型。例如,

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 $ 

如果您要给出的答案是“复制函数”,那么我该怎么做 删除原始复制构造函数/操作符?和我一起覆盖它 自定义复制功能?或者以其他方式阻止人们使用它?

停止使用 C++ 编程;开始用 Go 编程。按照设计,Go 不是 C++。

“复制构造函数/操作符”和“用自定义函数覆盖它”是 C++ 概念。

参考资料:

The Go Programming Language Specification

Blocks

Declarations and scope

Exported identifiers

【讨论】:

  • 这个解决方案有一些问题。 a)这意味着我必须在单独的包中声明结构。 b)我依靠用户知道他不应该按值传递结构,而是使用复制/克隆方法....我希望会有一个黑客来覆盖或至少删除默认的复制构造函数。 ...但似乎没有,所以如果在 1-2 天内没有更好的答案,我会将此标记为答案
  • 另外,很抱歉使用 C++ 概念,但我真的不知道 Go 甚至 C 的语义,最后,C++ 拥有 Go 的所有概念,所以它很容易映射从 Go 到 C++ 的功能,即使 GO 可能对复制构造函数/操作有不同的名称......它仍然是一个在语言中以某个名称存在的概念,我认为它很容易将两者联系起来。
猜你喜欢
  • 1970-01-01
  • 2012-03-07
  • 2013-11-26
  • 2011-03-19
  • 2018-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多