继承(Inheritance)
Solidity通过复制包括多态的代码来支持多重继承。
所有函数调用是虚拟(virtual)
的,这意味着最远的派生方式会被调用,除非明确指定了合约。
当一个合约从多个其它合约那里继承,在区块链上仅会创建一个合约,在父合约里的代码会复制来形成继承合约。
基本的继承体系与python
有些类似,特别是在处理多继承上面。
下面用一个例子来详细说明:
pragma solidity ^0.4.0;
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
// Use "is" to derive from another contract. Derived
// contracts can access all non-private members including
// internal functions and state variables. These cannot be
// accessed externally via `this`, though.
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
// These abstract contracts are only provided to make the
// interface known to the compiler. Note the function
// without body. If a contract does not implement all
// functions it can only be used as an interface.
contract Config {
function lookup(uint id) returns (address adr);
}
contract NameReg {
function register(bytes32 name);
function unregister();
}
// Multiple inheritance is possible. Note that "owned" is
// also a base class of "mortal", yet there is only a single
// instance of "owned" (as for virtual inheritance in C++).
contract named is owned, mortal {
function named(bytes32 name) {
Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);
NameReg(config.lookup(1)).register(name);
}
// Functions can be overridden by another function with the same name and
// the same number/types of inputs. If the overriding function has different
// types of output parameters, that causes an error.
// Both local and message-based function calls take these overrides
// into account.
function kill() {
if (msg.sender == owner) {
Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);
NameReg(config.lookup(1)).unregister();
// It is still possible to call a specific
// overridden function.
mortal.kill();
}
}
}
// If a constructor takes an argument, it needs to be
// provided in the header (or modifier-invocation-style at
// the constructor of the derived contract (see below)).
contract PriceFeed is owned, mortal, named("GoldFeed") {
function updateInfo(uint newInfo) {
if (msg.sender == owner) info = newInfo;
}
function get() constant returns(uint r) { return info; }
uint info;
}
上面的例子的named
合约的kill()
方法中,我们调用了motal.kill()
调用父合约的销毁函数(destruction)
。但这样可能什么引发一些小问题。
pragma solidity ^0.4.0;
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() { /* do cleanup 1 */ mortal.kill(); }
}
contract Base2 is mortal {
function kill() { /* do cleanup 2 */ mortal.kill(); }
}
contract Final is Base1, Base2 {
}
对Final.kill()
的调用只会调用Base2.kill()
,因为派生重写,会跳过Base1.kill
,因为它根本就不知道有Base1
。一个变通方法是使用super
。
pragma solidity ^0.4.0;
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() { /* do cleanup 1 */ super.kill(); }
}
contract Base2 is mortal {
function kill() { /* do cleanup 2 */ super.kill(); }
}
contract Final is Base2, Base1 {
}
如果Base1
调用了函数super
,它不会简单的调用基类的合约函数,它还会调用继承关系图谱上的下一个基类合约,所以会调用Base2.kill()
。需要注意的最终的继承图谱将会是:Final,Base1,Base2,mortal,owned。使用super时会调用的实际函数在使用它的类的上下文中是未知的,尽管它的类型是已知的。这类似于普通虚函数查找(ordinary virtual method lookup)
基类构造器的方法(Arguments for Base Constructors)
派生的合约需要提供所有父合约需要的所有参数,所以用两种方式来做,见下面的例子:
pragma solidity ^0.4.0;
contract Base {
uint x;
function Base(uint _x) { x = _x; }
}
contract Derived is Base(7) {
function Derived(uint _y) Base(_y * _y) {
}
}
或者直接在继承列表中使用is Base(7)
,或像修改器(modifier)
使用方式一样,做为派生构造器定义头的一部分Base(_y * _y)
。第一种方式对于构造器是常量的情况比较方便,可以大概说明合约的行为。第二种方式适用于构造的参数值由派生合约的指定的情况。在上述两种都用的情况下,第二种方式优先(一般情况只用其中一种方式就好了)。
多继承与线性化(Multiple Inheritance and Linearization)
实现多继承的编程语言需要解决几个问题,其中之一是菱形继承问题
又称钻石问题
,如下图。
Solidity的解决方案参考Python
,使用C3_linearization来强制将基类合约转换一个有向无环图(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
(因为继承定义的顺序是A,X
),但A
自身又是重写X
的,所以这是一个不可解决的矛盾。
一个简单的指定基类合约的继承顺序原则是从most base-like
到most derived
。
继承有相同名字的不同类型成员
当继承最终导致一个合约同时存在多个相同名字的修改器或函数,它将被视为一个错误。同新的如果事件与修改器重名,或者函数与事件重名都将产生错误。作为一个例外,状态变量的getter
可以覆盖一个public
的函数。