Een kopieerconstructor implementeren voor Java, Python, Golang en Rust

Crowds of fun-seekers exploring a city on foot, "

Arjan Franzen

February 2022

Logo da Acclaim com textos em Português trad

Een kopie constructor is een speciaal type constructor in object-georiënteerde talen zoals C++ en Java dat een nieuw object maakt door het te initialiseren met een bestaand object. De kopieerconstructor wordt gebruikt om een kopie van een object te maken op een manier die onafhankelijk is van het originele object, wat betekent dat wijzigingen aan de kopie geen invloed hebben op het origineel.

Als je vergeet een kopieerconstructor te implementeren en de standaardconstructor van de programmeertaal gebruikt, zal de kopieerconstructor een ondiepe kopie uitvoeren.

Een ondiepe kopie kopieert de waarden van de gegevensleden van het oorspronkelijke object naar het nieuwe object, maar als de gegevensleden verwijzingen bevatten, worden de verwijzingen gekopieerd, niet de waarden waarnaar ze wijzen. Dit betekent dat het nieuwe object naar dezelfde geheugenlocatie zal wijzen als het originele object, en als je het nieuwe object wijzigt, wijzig je ook het originele object. Dit kan leiden tot geheugenlekken, crashes of allerlei vervelende bugs.

Een diepe kopie is een type kopieerbewerking in programmeren dat een nieuw object maakt met dezelfde waarden als een bestaand object, maar met aparte geheugenlocaties. Bij een diepe kopie worden alle gegevensleden van het oorspronkelijke object gekopieerd, inclusief de waarden van pointervariabelen of referentietypen. Dit betekent dat wijzigingen aan het gekopieerde object geen invloed hebben op het oorspronkelijke object en vice versa.

Scenario's

In het Ondiep kopiëren scenario hieronder (links) zie je dat de Pointer naar de locatie '70' door zowel object 1 als object 2 wordt gerefereerd. Het wijzigen van de waarde op object 1 heeft ook invloed op object 2. Het vrijgeven van object 1 zal een probleem (crash) veroorzaken voor object 2 wanneer het verwijst naar een gederefereerde geheugenlocatie.

In het Diep Kopiëren scenario wordt de inhoud van object '70' gekopieerd en krijgt het een eigen locatie. Object 1 en Object 2 zijn nu volledig gescheiden en kunnen elkaar niet negatief beïnvloeden.

"Západoslovenská energetika logo &

Ondiepe kopie vs Diepe kopie van een object

Oorsprong van de kopieerconstructor in C++

In C++ is de kopieerconstructor verplicht omdat C++ een standaard kopieerconstructor genereert als er geen is gedefinieerd. Deze standaardconstructor voert een diepe kopie van het object uit. Dit kan tot problemen leiden als het object pointers of dynamische geheugentoewijzingen bevat, omdat de ondiepe kopie de pointer kopieert in plaats van de feitelijke gegevens.

De kopieerconstructor in C++ werkt door een verwijzing naar een bestaand object te nemen en een nieuw object te maken dat een kopie is van het origineel. Hij kan als volgt worden gedefinieerd:

Logo da Acclaim com textos em Português trad

een andere constructeur

1class MyClass {
2public:
3   // Default constructor
4   MyClass();
5
6   // Copy constructor
7   MyClass(const MyClass& other);
8};
9
10// Implementation of copy constructor
11MyClass::MyClass(const MyClass& other) {
12   // Copy data members from 'other' to 'this'
13}

Python

In Python is een kopieerconstructor niet verplicht omdat Python een ingebouwde copy-module biedt die functies biedt voor het maken van kopieën van objecten. Echter, in bepaalde scenario's, zoals bij het werken met muteerbare objecten zoals lijsten of woordenboeken, kan een kopieerconstructor nuttig zijn. Een voorbeeldimplementatie van een kopieerconstructor in Python is als volgt:

1class MyClass:
2   def __init__(self, value):
3       self.value = value
4
5   # Gekopieerde constructor
6   def __init__(self, other):
7       self.value = other.value

Java

In Java is een kopieerconstructor cruciaal als je een diepe kopie van een object moet maken. Als het object muteerbare objecten bevat, dan zal de standaard kopieerconstructor geen kopie van het muteerbare object maken, maar eerder een kopie van zijn referentie, wat tot problemen leidt als het oorspronkelijke object wordt gewijzigd. Een voorbeeldimplementatie van een kopieerconstructor in Java is als volgt:

1public class MyClass {
2   private int value;
3
4   // Standaard constructor
5   public MyClass(int value) {
6       this.value = value;
7   }
8
9   // Gekopieerde constructor
10   public MyClass(MyClass other) {
11       this.value = other.value;
12   }
13}

Golang

In Go kan een kopieerconstructor worden geïmplementeerd door een nieuwe functie te definiëren die een pointer naar een bestaand object neemt en een nieuw object retourneert dat een kopie is van het origineel. De nieuwe functie kan als volgt worden gedefinieerd:

1type MyClass struct {
2   value int
3}
4
5// Gekopieerde constructor
6func NewMyClass(other *MyClass) *MyClass {
7   return &MyClass{value: other.value}
8}

In Go wordt een kopieerconstructor niet zo vaak gebruikt als in andere talen, omdat Go een ingebouwd mechanisme biedt voor het kopiëren van waarden met behulp van de = operator. Wanneer een waarde wordt toegewezen aan een nieuwe variabele, wordt een nieuwe kopie van de waarde gemaakt. Echter, als de struct pointervelden bevat, creëert dit gedrag mogelijk geen diepe kopie en kan het leiden tot onverwacht gedrag.

Daarom kan een kopieerconstructor in Go handig zijn als je een diepe kopie moet maken van een struct die pointervelden bevat of als je een specifieke manier wilt definiëren om een struct te kopiëren. Als een struct bijvoorbeeld een map of een slice bevat, zal het gebruik van de = operator om de struct te kopiëren een nieuwe kopie van de verwijzing naar de map of slice maken, maar geen nieuwe kopie van de gegevens zelf. In dit geval kan een kopieerconstructor worden gebruikt om een nieuwe kopie van de gegevens te maken.

1type MyClass struct {
2   values []int
3}
4
5// Gekopieerde constructor
6func NewMyClass(other *MyClass) *MyClass {
7   values := make([]int, len(other.values))
8   copy(values, other.values)
9   return &MyClass{values: values}
10}

Hoewel een kopieerconstructor niet zo gebruikelijk is in Go als in andere talen, kan het handig zijn als je een diepe kopie moet maken van een struct die pointervelden bevat of als je een specifieke manier wilt definiëren om een struct te kopiëren.

Rust

In Rust is een kopieerconstructor niet nodig omdat Rust ownership en borrowing gebruikt om geheugenveiligheid te garanderen. Rust biedt echter een Clone eigenschap die gebruikt kan worden om een kopie van een object te maken. Een voorbeeldimplementatie van Clone in Rust is als volgt:

1#[derive(Clone)]
2struct MyClass {
3   value: i32,
4}
5
6fn main() {
7   let a = MyClass { value: 42 };
8   let b = a.clone(); // Maakt een kopie van 'a'
9}

Conclusion

Samengevat wordt een kopieerconstructor gebruikt om een nieuw object te maken door het te initialiseren met een bestaand object. In C++ is het verplicht om ervoor te zorgen dat een diepe kopie wordt uitgevoerd, terwijl het in Python, Java en Rust niet altijd nodig is, maar in bepaalde scenario's nuttig kan zijn.

background

Softwareontwikkeling ontmoeilijken