Was ist die Ausführungsreihenfolge von geerbten Konstruktoren in Solidity?

Ich lese den Abschnitt über geerbte Konstruktoren in der Dokumentation hier .

Das gegebene Beispiel verwirrt mich, als das Konzept zu verstehen, und es erklärt nicht den wichtigsten Teil, die Ausführungsreihenfolge.

Um die Dokumentation zusammenzufassen, heißt es im folgenden Beispiel:

pragma solidity ^0.4.0;

contract Base {
  uint x;
  function Base(uint _x) { x = _x; }
}


contract Derived1 is Base(7) {
  function Derived1(uint _y) {
  }
}

contract Derived2 is Base{
  function Derived2(uint _y) Base(_y * _y) {
  }
}

Derived1erbt Base(7)den Konstruktor und Derived2verwendet eine Modifikator-ähnliche Syntax von Base(_y * _y).

Aber was es NICHT erklärt, ist, wie sie tatsächlich ausgeführt werden. Nehmen wir ein Beispiel

contract Base {
  public uint x;
  function Base(uint _x) { x = _x; }
}


contract Derived1 is Base(7) {
  function Derived1(uint _y) {
    x = _y;
  }
}

contract Derived2 is Base {
  function Derived2(uint _y) Base(_y * _y) {
    x = _y;
  }
}

Wird das x = _yin jeder Vererbung VOR dem Basiskonstruktor ausgeführt? oder danach?

Normalerweise verwenden Sie in jeder objektorientierten orientierten Programmierung Notationen super(), die explizit angeben, wie der übergeordnete Konstruktor ausgeführt wird.

Im Falle von Ziel-c

- (void) init {
  [super init];
  // do something
}

und

- (void) init {
  // do something
  [super init];
}

machen einen großen Unterschied, da die Ausführungsreihenfolge unterschiedlich ist. Und das //do somethingTeil kann sogar das Ergebnis von verwenden [super init].

Wie funktioniert dieses Ding in Solidity? Wenn möglich, teilen Sie bitte auch die Quelle für die Erklärung mit. Ich kann das in der Dokumentation nicht finden, also weiß ich nicht, wo ich es sonst finden kann.

Bitte sagen Sie auch nicht, dass es sich um ein Duplikat handelt, und verwenden Sie ein Beispiel, das darauf hinweist, wie Konstruktoren bei der Vererbung verwendet werden. Die Syntax kenne ich bereits. Ich frage ausdrücklich nach dem Hinrichtungsbefehl, und wo dieser offiziell dokumentiert ist. Zum Beispiel habe ich diese Antwort bereits gelesen ethereum.stackexchange.com/questions/16318/… aber das erklärt nicht die Reihenfolge, die der Kern dieser Frage ist.
Meinten Sie in Ihrem Code, in den Verträgen Derived1und Derived2, die Funktionsnamen als Derived1bzw. Derived2, sodass sie Konstruktoren sind, anstatt Derivedin beiden?
@AjoyBhatia danke für den Hinweis! Habe es gerade repariert.

Antworten (2)

Ich konnte kein offizielles Dokument finden, um dies zu erklären. Aber wenn wir uns auf den Code, den Ablauf des Programms beziehen, können wir davon ausgehen, dass der Konstruktor von Basevor dem DerivedKonstruktor aufgerufen wird.

In der ersten sieht Derived1 is Base(7)die Base(7)Syntax wie der Konstruktor aus, wobei der Wert 7übergeben wird und wahrscheinlich aufgerufen wird, bevor der Derived1Konstruktor aufgerufen wird, wenn die deklarierte Reihenfolge berücksichtigt wird.

In der zweiten, da die Dokumente sagen,

die Art und Weise, wie ein Modifikator aufgerufen würde

und da immer die Modifikatoren ausgeführt werden, bevor wir in den Funktionskörper gehen, können wir zu dem Schluss kommen, dass Base(7)zuerst ausgeführt wird.

Mit den obigen Annahmen habe ich den folgenden Code in Remix mit _y=10bei der Vertragserstellung ausprobiert. Die getXFunktion in beiden Verträgen hat einen Wert von zurückgegeben 10, aber nicht 7in Derived1oder 100was (_y*_y)in ist Derived2. Das bedeutet, dass Baseder Konstruktor zuerst ausgeführt wird und der Wert von im Körper der Konstruktoren durch xüberschrieben wird. Ich denke, dies beweist, dass der Konstruktor zuerst erwartet wird.10x = _y;DerivedBase

pragma solidity ^0.4.0;

contract Base {
  uint x;
  function Base(uint _x) { x = _x; }
}


contract Derived1 is Base(7) {
  function Derived1(uint _y) {
      x = _y;
  }

  function getX() constant returns(uint){
      return x;
  }
}

contract Derived2 is Base {
  function Derived2(uint _y) Base(_y * _y) {
      x = _y;
  }

  function getX() constant returns(uint){
      return x;
  }

}

x = _y(Wenn Sie in Konstruktoren keine Derivedzurückgegebenen Werte haben, sind 7und 100)

Aktuelle Solidity-Dokumente liefern die Antwort in diesem Absatz: https://docs.soliditylang.org/en/v0.8.13/contracts.html#multiple-inheritance-and-linearization

Grundsätzlich gilt: Solidity garantiert, dass alle Konstruktoren der Basisklassen vor einem Konstruktor einer abgeleiteten Klasse aufgerufen werden. Und der Konstruktor der abgeleiteten Klasse kann die Reihenfolge, in der Konstruktoren der Basisklassen von Solidity aufgerufen werden, nicht ändern.

Dies ist jedoch etwas kompliziert, da Solidity mit Mehrfachvererbung umgehen muss. Wie in der Dokumentation erläutert, wird der Vererbungsgraph für die abgeleitete Klasse mit C3-Linearisierung linearisiert , was eine geordnete Liste von Klassen ergibt, in der jede abgeleitete Klasse nach all ihren Basisklassen erscheint. Die Konstruktoren werden in der Reihenfolge der Liste aufgerufen. Wenn Sie mehr als eine Basisklasse haben, wird die Reihenfolge, in der sie in der Liste erscheinen, von der Reihenfolge beeinflusst, in der sie in der Vererbungsliste aufgeführt sind (dh nach dem is).

Zitat aus der Dokumentation:

Ein Bereich, in dem die Vererbungslinearisierung besonders wichtig und vielleicht nicht so klar ist, ist, wenn es mehrere Konstruktoren in der Vererbungshierarchie gibt. Die Konstruktoren werden immer in der linearisierten Reihenfolge ausgeführt, unabhängig von der Reihenfolge, in der ihre Argumente im Konstruktor des erbenden Vertrags bereitgestellt werden. Zum Beispiel:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Base1 {
    constructor() {}
}

contract Base2 {
    constructor() {}
}

// Constructors are executed in the following order:
//  1 - Base1
//  2 - Base2
//  3 - Derived1
contract Derived1 is Base1, Base2 {
    constructor() Base1() Base2() {}
}

// Constructors are executed in the following order:
//  1 - Base2
//  2 - Base1
//  3 - Derived2
contract Derived2 is Base2, Base1 {
    constructor() Base2() Base1() {}
}

// Constructors are still executed in the following order:
//  1 - Base2
//  2 - Base1
//  3 - Derived3
contract Derived3 is Base2, Base1 {
    constructor() Base1() Base2() {}
}