티스토리 뷰

반응형

솔리디티에서 override 키워드는 솔리디티 0.6.0 버전 이후에 추가된 기능으로, 상위 컨트랙트(contract)에서 정의된 함수를 하위 컨트랙트(contract)에서 재정의(override)할 때 사용한다. 

상위 컨트랙트에서 정의된 함수와 같은 이름과 형식을 갖는 함수를 하위 컨트랙트에서 정의하면, 이를 override 했다고 한다. 이때, override한 함수는 상위 컨트랙트에서 정의된 함수와 이름, 매개변수 형식, 반환값 형식이 동일해야 한다.

 

즉, 상속받은 함수를 덮어쓰기 위해 같은 이름, 같은 매개변수를 가진 함수를 다시 정의할 때 사용하는 것이다. 솔리디티는 override를 통해 컨트랙트 간의 상속 관계를 구현할 수 있다.


override(오버라이드, 재정의)

override된 함수는 상위 컨트랙트의 함수를 대체하게 되며, 하위 컨트랙트의 객체에서 호출되면 override된 함수가 호출된다. 

다음은 override 예제 코드이다.

pragma solidity ^0.8.0;

contract B {
    function foo(uint256 x) public virtual returns (uint256) {
        return x * 2;
    }
}

contract A is B {
    function foo(uint256 x) public override returns (uint256) {
        uint256 y = super.foo(x);
        return y + 1;
    }
}

위 코드에서 컨트랙트 A는 컨트랙트 B를 상속받았고, A에서 override 키워드를 사용하여 B 컨트랙트의 foo 함수를 재정의하고 있다. 

 

이때, B 컨트랙트에서 재정의 해야하는 함수에는 반드시 virtual이라는 키워드를 적어야하고, A 컨트랙트에서 재정의 된 함수에는 override라고 키워드를 적어야한다.

 

override된 foo 함수 내부에서는 super 키워드를 사용하여 상위 컨트랙트의 함수를 호출할 수 있다. A에서 override된 foo 함수 내부에서는 super.foo로 상위 컨트랙트(B) foo 함수를 호출하고 그 반환값에 1을 더하여 최종적으로 반환한다.

 

이렇게 override와 virtual을 사용하면 컨트랙트를 상속하고 새로운 기능을 추가하는 것이 더욱 간편해진다. 단, virtual 함수는 하위 컨트랙트에서 재정의되기 때문에 상위 컨트랙트에서 불필요한 로직이 들어가지 않도록 주의해야 한다.


virtual 없이 override(재정의) 하는 방법

virtual 키워드는 위에서 말했듯이 다른 컨트랙트에서 오버라이딩(overriding)될 수 있는 함수임을 나타낸다. 상위 컨트랙트의 함수에서 virtual 키워드를 사용하면 하위 컨트랙트에서 override 키워드와 함께 다시 재정의 할 수 있다.

 

하지만 상위 컨트랙트 함수에 virtual 키워드가 없더라도 하위 컨트랙트에서 함수를 재정의 할 수도 있다. 아래 코드를 보자.

// 상위 컨트랙트
contract Parent {
    function foo() public {
        // 상위 컨트랙트의 foo 함수 구현
    }
}

// 하위 컨트랙트
contract Child is Parent {
    function foo() public {
        // 하위 컨트랙트에서 foo 함수를 재정의
        // 상위 컨트랙트의 foo 함수와 동일한 이름을 사용하고, 구현 내용만 다름
    }
}

virtual 없이 함수를 재정의하고 싶다면, 상위 컨트랙트 함수와 똑같은 이름의 함수를 하위 컨트랙트에 정의하면 된다.

그러나 이 경우엔 하위 컨트랙트에서 함수를 재정의하면, 상위 컨트랙트의 함수가 가려져서(shadowed) 하위 컨트랙트에서 재정의한 함수가 호출된다. 이 문제는 아래와 같이 해결할 수 있다.

// 하위 컨트랙트
contract Child is Parent {
    function foo() public new {
        // 하위 컨트랙트에서 foo 함수를 새롭게 구현하고, 상위 컨트랙트의 foo 함수를 가려서(shadow) 호출되지 않도록 한다.
    }
}

이렇게 new 키워드를 사용해서 하위 컨트랙트에서 새로운 함수를 정의하면, 상위 컨트랙트의 함수가 가려지는 문제를 해결할 수 있다.


또 다른 방법이 있다. 바로 인터페이스(interface)를 쓰는 방법이다.

인터페이스를 상속받는 컨트랙트는 해당 인터페이스에 정의된 모든 함수들을 구현해야 한다. 구현시에는 함수 시그니처(signature)와 이름을 일치시켜야 한다. 

 

인터페이스의 함수들은 virtual을 쓸 필요 없지만 인터페이스를 상속받는 컨트랙트는 override를 표기해줘야 한다. override 키워드를 생략할 수 있지만 이때는 인터페이스 함수와 일치하는 이름과 매개변수를 사용해야한다. 함수의 형태와 매개변수 등을 일치시키면 인터페이스의 함수를 구현하는 것으로 인식된다. 하지만 override 키워드를 사용하면 재정의된 함수임을 더 명시적으로 나타낼 수 있으므로 코드의 가독성을 높일 수 있다. 또한 재정의하지 않은 상위 컨트랙트 함수를 재정의하려는 경우, 컴파일러가 컴파일러 경고를 표시하여 override를 생략하지 않도록 유도할 수 있다.

 

인터페이스의 예를 들어보자. 

// IExample 인터페이스 정의
interface IExample {
    function exampleFunction(uint256 param) external returns (uint256);
}

// ExampleChild 컨트랙트 구현
contract ExampleChild is IExample {
    // IExample의 exampleFunction 재정의
    function exampleFunction(uint256 param) external override returns (uint256) {
        // 구현 내용 작성
        return param * 2;
    }
}

 

위 코드에서 IExample 인터페이스에서 exampleFunction을 정의하고 있다. ExampleChild 컨트랙트에서는 exampleFunction을 override하여 재정의하고 있다. 이때 인터페이스 함수와 동일한 이름, 매개변수, 접근제한자를 사용하고 있으며 override를 붙여서 함수를 다시 구현하였다.

반응형
댓글
공지사항