Solidityメモ: 多重継承
Solidityの多重継承がよくわからなかったので整理。 (C#には多重継承が無いので。。。)
- 多重継承
- ダイヤモンド継承
- 親と祖父を継承
- virtualとoverride
- 多重継承とoverride
- ダイヤモンド継承とoverride
- 実践編: ERC721EnumerableとERC721Burnableを継承する
多重継承
contract A {} contract B {} contract C is A {} // これは普通の継承 contract D is A, B {} // これが多重継承
ダイヤモンド継承
contract A {} contract B is A {} contract C is A {} contract D is B, C {} // この場合 A-B-C-D の順番 contract E is C, B {} // この場合 A-C-B-E の順番
この順番は、superでひとつ上の関数を呼び出す時に重要になる。
Multiple Inheritance and Linearization(多重継承と線形化) — Solidity 0.8.7 documentation
親と祖父を継承
contract A {} contract B is A {} contract C is A, B {} contract C is B, A {} // これはエラー (ベースクラスを先に書く必要がある)
親(B)と祖父(A)の両方を継承しているパターン。
親だけでいいんじゃないのと思うのだがよく見かける。何か意味があるのだろうか?
contract MyNFT is ERC721, ERC721Enumerable {}
とか
virtualとoverride
関数を子でオーバーライドさせたいなら、関数にvirtualを付ける。 オーバーライドした方は、overrideを付ける。
親の関数をオーバーライドしつつ、子にオーバーライドさせたいなら、virtual override と書く。 ここはC#とは違うところなので注意。(C#ならoverrideだけでよい)
contract A { function hoge() public virtual {} } contract B is A { function hoge() public virtual override {} } contract C is B { function hoge() public override {} }
多重継承とoverride
同じ関数がAとBの両方にある場合は、Cはオーバーライドしなければならない。このとき override (A, B)
のように書く必要がある。
contract A { function hoge() public virtual {} } contract B { function hoge() public virtual {} } contract C is A, B { function hoge() public override (A, B) { super.hoge(); // B.hoge()を呼ぶ // A.hoge(); // 直接A.hoge()を呼ぶこともできる } }
Cにhoge関数がないと下記エラーが出る。
TypeError: Derived contract must override function "hoge". Two or more base classes define function with same name and parameter types.
ダイヤモンド継承とoverride
A-(B-C)-D タイプのダイアモンド継承では、同じ関数がAとBにあり、Cにない場合でも、Dはオーバーライドしなければならない。このときは override (A, B)
と書く。
contract A { function hoge() public virtual {...} } contract B is A { function hoge() public override virtual {...} } contract C is A { // hoge関数を持たない } contract D is B, C { function hoge() public override (A, B) { super.hoge(); // B.hoge()を呼ぶ } }
実践編: ERC721EnumerableとERC721Burnableを継承する
ERC721EnumerableとERC721Burnableは、どちらもERC721を継承している。
そのため、この2つを多重継承する場合には、
_beforeTokenTransfer
とsupportsInterface
関数をオーバーライドしなければならない。
これらの関数はERC721とERC721Enumerableには存在するが、ERC721Burnableには存在しない。
上の説明の
- A = ERC721
- B = ERCEnumerable
- C = ERCBurnable
- D = MyNFT
と考えるとわかりやすい。
contract MyNFT is ERC721Enumerable, ERC721Burnable { constructor() ERC721("MyNFT", "MYNFT") {} // ※ function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) { super._beforeTokenTransfer(from, to, tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) { return super.supportsInterface(interfaceId); } }
※ ちなみに ERC721EnumerableとERC721Burnable はコンストラクタを持たず、その親のERC721は引数ありのコンストラクタを持つため、 このような書き方になる。