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方法的查找相似。
派生的合约需要为父构造函数提供所有的参数。有两种方式:
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)
。使用原则:
允许多重继承的编程语言必须处理这几个问题,其中一个是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
(定义A
,X
这个顺序),但A
本身的要求重写X
,这是一个矛盾,不能解决。
一个简单的规则是要指定父类中的顺序从左到右为“most base-like”到“most derived”。
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 函数。