Solidity語言學習筆記————34、繼承

FLy_鵬程萬里發表於2018-07-03

繼承

Solidity通過複製包括多型性的程式碼來支援多重繼承。 
除非合約是顯式給出的,所有的函式呼叫都是虛擬的,絕大多數派生函式可被呼叫。 
當一個合約繼承自多個合約時,只會在區塊鏈上建立單個合約,並將所有父合約中的程式碼複製到建立的合約中。 
Solidity的繼承與Python非常相似,特別是多繼承。 
以下是個例子:

pragma solidity ^0.4.16;

contract owned {
    function owned() { owner = msg.sender; }
    address owner;
}
// 使用`is`繼承另一個合約。
// 子合約可以訪問所有非私有成員,包括
// 內部函式和狀態變數。 
// 不過,不能通過`this`來外部訪問這些。
contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}
// 這些抽象合約僅用於建立編譯器已知的介面。 
// 注意函式沒有函式體。 
// 如果合約沒有實現全部函式,那它只能是介面。
contract Config {
    function lookup(uint id) public returns (address adr);
}

contract NameReg {
    function register(bytes32 name) public;
    function unregister() public;
 }
// 可以多重繼承。 
// 請注意,`owned`也是`mortal`的父類,
// 但只有一個`owned`的例項(類似C++中的虛擬繼承)。
contract named is owned, mortal {
    function named(bytes32 name) {
        Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
        NameReg(config.lookup(1)).register(name);
    }
    // 函式可以被另一個具有相同名稱
    // 和相同數量/型別的輸入引數的函式覆蓋。
    // 如果重寫函式有不同輸出引數的型別,會發生錯誤。
    // 本地和基於訊息的函式呼叫都會將這些覆蓋函式考慮在內。
    function kill() public {
        if (msg.sender == owner) {
            Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
            NameReg(config.lookup(1)).unregister();
            // 仍然可以呼叫特定的重寫函式。
            mortal.kill();
        }
    }
}
// 如果建構函式接受引數,引數需要在頭部中提供。
// 或者在派生合約的構造器裡
// 使用修飾符呼叫方式modifier-invocation-style見下文
contract PriceFeed is owned, mortal, named("GoldFeed") {
   function updateInfo(uint newInfo) public {
      if (msg.sender == owner) info = newInfo;
   }

   function get() public view returns(uint r) { return info; }

   uint info;
}

注意:在上文中,我們使用mortal.kill() 來“forward” 析構請求。這種做法是有問題的,請看下面的例子:

pragma solidity ^0.4.0;

contract owned {
    function owned() public { owner = msg.sender; }
    address owner;
}

contract mortal is owned {
    function kill() public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is mortal {
    function kill() public { /* do cleanup 1 */ mortal.kill(); }
}

contract Base2 is mortal {
    function kill() public { /* do cleanup 2 */ mortal.kill(); }
}

contract Final is Base1, Base2 {
}
Final.kill() 將呼叫Base2.kill作為最後的派生重寫,但這個函式繞開了Base1.kill。因為它不知道有Base1。這種情況下要使用 super
pragma solidity ^0.4.0;

contract owned {
    function owned() public { owner = msg.sender; }
    address owner;
}

contract mortal is owned {
    function kill() public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is mortal {
    function kill() public { /* do cleanup 1 */ super.kill(); }
}


contract Base2 is mortal {
    function kill() public { /* do cleanup 2 */ super.kill(); }
}

contract Final is Base1, Base2 {
}

Base1 呼叫了super函式,它不是簡單地呼叫基本合約之一的函式, 它是呼叫最後繼承關係的下一個基本合約的函式。所以它會呼叫Base1.kill()(注意,最後的繼承順序是–從最後的派生合約開始:Final, Base1, Base2, mortal, owned)。當使用類的上下文中super不知道的情況下,真正的函式將被呼叫,雖然它的型別已經知道。這個和普通的virtual方法的查詢相似。

父建構函式的引數(Arguments for Base Constructors)

派生的合約需要為父建構函式提供所有的引數。有兩種方式:

pragma solidity ^0.4.0;

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

contract Derived is Base(7) {
    function Derived(uint _y) Base(_y * _y) public {
    }
}

  • 第一種是直接在繼承列表裡實現is Base(7)
  • 第二種是在派生的構造器的頭部,修飾符被呼叫時實現Base(_y * _y)

使用原則:

  • 如果建構函式引數是一個常量,並且定義了合約的行為或描述了它的行為,第一種方式比較方便。
  • 如果父建構函式引數依賴於派生合約的建構函式,則必須使用第二種方法。
  • 如果在這個荒謬的例子中,這兩個地方都被使用,修飾符樣式的引數優先

多繼承和線性化(Multiple Inheritance and Linearization)

允許多重繼承的程式語言必須處理這幾個問題,其中一個是Diamond問題。Solidity是沿用Python的方式, 使用C3線性化,在基類的DAG強制使用特定的順序。這導致單調但不允許某些繼承關係。特別是,is指令中給出父類的順序很重要。在下面的程式碼中,Solidity會報錯:“Linearization of inheritance graph impossible”。

// 以下程式碼無法編譯

pragma solidity ^0.4.0;

contract X {}
contract A is X {}
contract C is A, X {}

原因是C要求X來重寫A(定義AX這個順序),但A本身的要求重寫X,這是一個矛盾,不能解決。

一個簡單的規則是要指定父類中的順序從左到右為“most base-like”到“most derived”。

繼承不同型別的同名成員(Inheriting Different Kinds of Members of the Same Name)

When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error. This error is produced also by an event and a modifier of the same name, and a function and an event of the same name. As an exception, a state variable getter can override a public function.

當繼承導致存在有相同名稱的函式和修飾符的合約時,會發生錯誤。 這個錯誤也會由同名的事件和修飾符以及同名的函式和事件產生的。

例外是狀態變數getter可以覆蓋public 函式。

相關文章