C++ method

3 분 소요

method

C++에서 제공하는 메서드의 종류는 다양하다.

static method

메서드도 데이터 멤버처럼 특정 객체 단위가 아닌 클래스 단위로 적용되는 것이 있다. 이를 static (정적, 스태틱) 메서드라 부르며 데이터 멤버를 정의하는 단계에 함꼐 작성한다. 객체 정보에 접근하지 않는 메서드를 static으로 정의한다.
class MyClass
{
    static std::string doubleToString(double value);
    static double stringToDobule(std::string_view string);
};
  • 구현 코드에서 앞부분에 static 키워드를 생략해도 된다.
  • static 메서드는 특정 객체에 대해 호출되지 않기 때문에 this 포인터를 가질 수 없다. 어떤 객체의 non-static 멤버에 접근하는 용도로 호출할 수 없다.
  • static 메서드는 근본적으로 일반 함수와 비슷하지만 클래스의 private static이나 protected static 멤버만 접근할 수 있다는 점은 다르다.
  • 같은 타입의 객체를 포인터나 레퍼른스로 전달하는 방법 등을 사용해서 그 객체를 static 메서드에서 볼 수 있게 만들었다면 클래스에서 non-static privateㄷ또는 protected 멤버에 접근할 수 있다.
NOTE_ C++17이전에는 읽기 전용 스트링을 받는 함수의 매개변수 타입을 쉽게 결정할 수 없었다. const char*로 지정하면 std::string을 사용하는 클라이언트에서 c_str() 이나 data()를 이용하여 std::stringconst char*로 변환해서 호출해야 한다. C++17부터는 std::string_view 클래스를 사용하면 이러한 문제를 해결할 수 있다. std::basic_string_view 클래스 템플릿의 인스턴스로서 <string_view> 헤더에 정의돼 있다. 만약 주어진 파일명에서 확장자만 뽑아서 리턴하는 함수를 string_view로 구현한다면 다음과 같이 구현하고,
std::string_view extractExtension(std::string_view fileName)
{
    return fileName.substr(fileName.rfind('.'));
}
모든 종류의 스트링에 적용할 수 있다.
std::string fileName = R"(c:\temp\my file.ext)";
std::cout << "C++ string: " << extractExtension(fileName) << endl;
const char* cString = R"(c:\temp\my file.ext)";
std::cout << "C string: " << extractExtension(cString) << endl;
cout << "Literal: " << extractExtension(R"(c:\temp\my file.ext)") << endl;

const method

const 객체란 값이 바뀌지 않는 객체를 말한다. const 객체나 이에 대한 레퍼런스 또는 포인터를 사용할 때 그 객체의 데이터 멤버를 절대로 변경하지 않는 메서드만 호출할 수 있다. 그렇지 않으면 컴파일 에러가 발생한다. 이처럼 어떤 메서드가 데이터 멤버를 변경하지 않는다고 보장하고 싶을 때 const 키워드를 붙인다. 예를들어 클래스 내의 값을 리턴하는 메서드를 const로 선언하면 다음과 같다.
class MyClass
{
    private:
        _myData;
    public:
        double getValue() const;
};
const는 메서드 프로토타입의 일부이기 때문에 다음과 같이 메서드를 구현하는 코드에서도 반드시 적어야 한다.
double MyClass::getValue() const
{
    return _myData;
}
  • 메서드를 const로 선언하면 이 메서드 안에서 객체의 내부 값을 변경하지 않겠다는 계약을 클라이언트 코드와 맺는 셈이다.
  • 데이터 멤버를 수정하는 메서드를 const로 선언하면 컴파일 에러가 발생한다.
  • static 메서드를 const로 선언해서도 안 된다. static 메서드는 근본적으로 클래스의 인스턴스에 속하지 않아 객체 내부의 값을 변경할 수 없기 때문에 static 메서드에 const 키워드를 붙이는 것은 의미가 없다.
  • 메서드에 const 키워드를 붙이면 그 메서드 안에서 각 데이터 멤버에 대한 const 레퍼런스를 가진 것처럼 작동한다. 따라서 데이터 멤버를 변경하는 코드가 나오면 컴파일 에러가 발생한다.
  • 객체를 const로 선언하지 않았다면 그 객체의 const 메서드와 non-const 메서드를 모두 호출할 수 있다. 반면 const로 선언했다면 그 객체의 const 메서드만 호출할 수 있다.

mutable data member

의미상으로는 const인 메서드에서 객체의 데이터 멤버를 변경하는 경우가 있다. 이렇게 해도 사용자 데이터에 아무런 영향을 미치지 않지만 엄연히 수정하는 것이기 때문에 이런 메서드를 const로 선언하면 컴파일 에러가 발생할 수 있다. 이럴 때는 횟수를 세는 카운터 변수를 mutable로 선언해서 컴파일러에 이 변수를 const 메서드에서 변경할 수 있다고 알려주면 된다. 다음과 같다.
class MyClass
{
    private:
        double _data;
        mutable size_t accessCounter = 0;
    double getData() const
    {
        return _data;
        accessCounter++;
    }
};

method overloading

한 클래스에 생성자를 여러 개 정의할 수 있다. 이렇게 정의한 생성자는 모두 이름은 같고 매개변수의 타입이나 개수만 다르다. 메서드나 함수도 이와 마찬가지로 매개변수의 타입이나 개수만 다르게 지정해서 이름이 같은 함수나 메서드를 여러 개 정의할 수 있다. 이를 오버로딩이라 부른다.
class MyClass
{
    public:
        void foo(double value);
        void foo(std::string_view value);
};
메서드 구현 코드에서 메서드 이름을 제외한 나머지는 그대로 둔다. 컴파일러가 foo()를 호출하기 코드를 발견하면 매개변수 정보륿 보고 어느 버전의 foo()를 호출할지 결정한다. 매개 변수가 string_view 타입이면 string 버전의 set()이 호출된다. 이를 오버로딩 결정 (overloading resolution)이라 한다.

const based overloading

const 기준으로 오버로딩할 수도 있다. 메서드를 두 개 정의할 때 이름과 매개변수는 같지만 하나는 const로 선언한다. 그러면 const 객체에서 이 메서드를 호출하면 const 메서드가 실행되고, non-const 객체에서 호출하면 non-const 메서드가 실행된다. 만약 const, non-const 의 구현 코드가 동일하다면 const_cast() 패턴을 적용한다. 다음과 같은 방법으로 구현하면 된다.
const MyClass* MyClass::getProperty(size_t x, size_t y) const
{
    return property[x][y];
}
MyClass& MyClass::getProperty(size_t x, size_t y)
{
    return const_cast<MyClass&>(std::as_const(*this).getProperty(x,y));
}
std::as_const() 함수는 C++17부터 추가되었다. 지원하지 않는다면 static_cast()를 사용한다.
MyClass& MyClass::getProperty(size_t x, size_t y)
{
    return const_cast<MyClass&>(static_cast<const MyClass&>(*this).getProperty(x,y));
}

명시적으로 오버로딩 제거하기

오버로딩된 메서드를 명시적으로 삭제할 수 있다. 그러면 특정한 인수에 대해서는 메서드를 호출하지 못하게 된다.
class MyClass
{
    public:
        void foo(int i);
        void foo(double d) = delete;
};
위와 같이 작성하면 만약 foo(3.5)와 같이 작성하면 컴파일 에러가 발생한다.

default 인수

메서드 오버로딩과 비슷한 기능으로 default라는 것도 있다. 이 기능을 이용하면 함수나 메서드의 프로토타입(선언)에 매개변수의 기본값을 지정할 수 있다. 사용자가 다른 값으로 지정한 인수를 전달하면 디폴드값은 무시된다.
class MyClass
{
    public:
        MyClass(int i = 100, std::string_view = "");
};

참고

  • 전문가를 위한 C++

태그:

카테고리:

업데이트: