멤버 함수

멤버 함수 (Member functions)

비정적 멤버 함수(non-static member function)static이나 friend 지정자 없이 클래스의 멤버 지정(member specification) 안에서 선언된 함수예요 (staticfriend의 효과는 각각 정적 멤버 함수와 friend 선언을 참고해요).

class S
{
    int mf1(); // non-static member function declaration
    void mf2() volatile, mf3() &&; // can have cv-qualifiers and/or a reference-qualifier
        // the declaration above is equivalent to two separate declarations:
        // void mf2() volatile;
        // void mf3() &&;

    int mf4() const { return data; } // can be defined inline
    virtual void mf5() final; // can be virtual, can use final/override
    S() : data(12) {} // constructors are member functions too
    int data;
};

int S::mf1() { return 7; } // if not defined inline, has to be defined at namespace

생성자, 소멸자, 변환 함수는 선언에 특별한 문법을 써요. 이 페이지에서 설명하는 규칙이 이 함수들에는 적용되지 않을 수 있어요. 각각의 페이지에서 자세한 내용을 확인하세요.

명시적 객체 멤버 함수(explicit object member function) 는 명시적 객체 매개변수(explicit object parameter)를 가진 비정적 멤버 함수예요. (C++23부터)

암시적 객체 멤버 함수(implicit object member function) 는 명시적 객체 매개변수가 없는 비정적 멤버 함수예요 (C++23 이전에는 이것이 유일한 종류의 비정적 멤버 함수였고 그래서 문헌에서 그냥 "비정적 멤버 함수"라고 불렸어요).

출처: cppreference

본문

설명 (Explanation)

비정적 멤버 함수에서만 쓸 수 있는 추가 문법 요소와 함께 어떤 함수 선언이든 허용돼요. 여기에는 pure-specifier, cv-한정자, ref-한정자, finaloverride 지정자(C++11부터), 멤버 이니셜라이저 목록이 포함돼요.

클래스 X의 비정적 멤버 함수는 이렇게 호출될 수 있어요.

  1. X 타입 객체에 클래스 멤버 접근 연산자를 사용해서
  2. X에서 파생된 클래스의 객체에
  3. X의 멤버 함수 본문 안에서 직접
  4. X에서 파생된 클래스의 멤버 함수 본문 안에서 직접

X 타입이 아니거나 X에서 파생된 타입이 아닌 객체에 X의 비정적 멤버 함수를 호출하면 미정의 동작을 일으켜요.

X의 비정적 멤버 함수 본문 안에서, X 또는 X의 기반 클래스의 비타입 비정적 멤버로 풀이되는 어떤 id-표현식 e(식별자 같은)는 멤버 접근 표현식 (*this).e(이미 멤버 접근 표현식의 일부가 아닌 경우)로 변환돼요. 이 변환은 템플릿 정의 맥락에서는 일어나지 않아서, 종속적으로 만들기 위해 이름 앞에 this->를 명시적으로 붙여야 할 수 있어요.

struct S
{
    int n;
    void f();
};

void S::f()
{
    n = 1; // transformed to (*this).n = 1;
}

int main()
{
    S s1, s2;
    s1.f(); // changes s1.n
}

X의 비정적 멤버 함수 본문 안에서, X 또는 X의 기반 클래스의 정적 멤버, 열거자, 중첩 타입으로 풀이되는 어떤 비한정 id 는 그에 대응하는 한정 id 로 변환돼요.

struct S
{
    static int n;
    void f();
};

void S::f()
{
    n = 1; // transformed to S::n = 1;
}

int main()
{
    S s1, s2;
    s1.f(); // changes S::n
}

cv-한정자가 있는 멤버 함수 (Member functions with cv-qualifiers)

암시적 객체 멤버 함수는 cv-한정자 시퀀스(const, volatile, 또는 constvolatile의 조합)로 선언될 수 있어요. 이 시퀀스는 함수 선언의 매개변수 목록 뒤에 와요. cv-한정자 시퀀스(또는 시퀀스 없음)가 다른 함수들은 타입이 다르므로 서로 오버로드될 수 있어요.

cv-한정자 시퀀스가 있는 함수의 본문 안에서 *this는 cv-한정돼요. 예를 들어 const 한정자가 있는 멤버 함수 안에서는 정상적으로 const 한정자가 있는 다른 멤버 함수만 호출할 수 있어요. const 한정자가 없는 멤버 함수는 const_cast를 적용하거나 this가 개입하지 않는 접근 경로를 통하면 여전히 호출될 수 있어요.

#include <vector>

struct Array
{
    std::vector<int> data;
    Array(int sz) : data(sz) {}

    // const member function
    int operator[](int idx) const
    {                     // the this pointer has type const Array*
        return data[idx]; // transformed to (*this).data[idx];
    }

    // non-const member function
    int& operator[](int idx)
    {                     // the this pointer has type Array*
        return data[idx]; // transformed to (*this).data[idx]
    }
};

int main()
{
    Array a(10);
    a[1] = 1;  // OK: the type of a[1] is int&
    const Array ca(10);
    ca[1] = 2; // Error: the type of ca[1] is int
}

ref-한정자가 있는 멤버 함수 (Member functions with ref-qualifier)

암시적 객체 멤버 함수는 ref-한정자가 없거나, lvalue ref-한정자(매개변수 목록 뒤의 & 토큰) 또는 rvalue ref-한정자(&& 토큰)로 선언될 수 있어요. 오버로드 결정 중에 cv-한정자 시퀀스가 있는 클래스 X의 암시적 객체 멤버 함수는 이렇게 취급돼요.

  • ref-한정자 없음: 암시적 객체 매개변수는 cv-한정 X에 대한 lvalue 참조 타입이며, 추가로 rvalue 암시적 객체 인자를 바인딩하는 것이 허용돼요.
  • lvalue ref-한정자: 암시적 객체 매개변수는 cv-한정 X에 대한 lvalue 참조 타입.
  • rvalue ref-한정자: 암시적 객체 매개변수는 cv-한정 X에 대한 rvalue 참조 타입.
#include <iostream>

struct S
{
    void f() &  { std::cout << "lvalue\n"; }
    void f() && { std::cout << "rvalue\n"; }
};

int main()
{
    S s;
    s.f();            // prints "lvalue"
    std::move(s).f(); // prints "rvalue"
    S().f();          // prints "rvalue"
}

참고: cv-한정과 달리 ref-한정은 this 포인터의 성질을 바꾸지 않아요. rvalue ref-한정 함수 안에서도 *this는 lvalue 표현식으로 남아요. (C++11부터)

가상 및 순수 가상 함수 (Virtual and pure virtual functions)

비정적 멤버 함수는 virtual 또는 순수 가상으로 선언될 수 있어요. 자세한 내용은 가상 함수추상 클래스를 봐요.

명시적 객체 멤버 함수 (Explicit object member functions)

멤버 함수의 첫 매개변수는 명시적 객체 매개변수(앞에 붙는 this 키워드로 표시)일 수 있어요. 단 다음 경우는 제외돼요.

  • 함수가 static 또는 virtual이거나,
  • 함수가 cv-한정자 또는 ref-한정자로 선언되거나,
  • 첫 매개변수가 함수 매개변수 팩일 때
struct X
{
    void foo(this X const& self, int i); // same as void foo(int i) const &;
//  void foo(int i) const &; // Error: already declared

    void bar(this X self, int i); // pass object by value: makes a copy of "*this"
};

멤버 함수 템플릿의 경우 명시적 객체 매개변수는 타입과 값 카테고리의 추론을 허용해요. 이 언어 기능을 "deducing this" 라고 불러요.

struct X
{
    template<typename Self>
    void foo(this Self&&, int);
};

struct D : X {};

void ex(X& x, D& d)
{
    x.foo(1);       // Self = X&
    move(x).foo(2); // Self = X
    d.foo(3);       // Self = D&
}

이 덕분에 const 멤버 함수와 비-const 멤버 함수를 중복 작성하지 않을 수 있어요. 예시는 배열 첨자 연산자를 봐요.

명시적 객체 멤버 함수의 본문 안에서는 this 포인터를 쓸 수 없어요. 모든 멤버 접근은 정적 멤버 함수에서처럼 첫 매개변수를 통해 이뤄져야 해요.

struct C
{
    void bar();

    void foo(this C c)
    {
        auto x = this; // error: no this
        bar();         // error: no implicit this->
        c.bar();       // ok
    }
};

명시적 객체 멤버 함수에 대한 포인터는 멤버 포인터가 아니라 보통의 함수 포인터 예요.

struct Y
{
    int f(int, int) const&;
    int g(this Y const&, int, int);
};

auto pf = &Y::f;
pf(y, 1, 2);              // error: pointers to member functions are not callable
(y.*pf)(1, 2);            // ok
std::invoke(pf, y, 1, 2); // ok

auto pg = &Y::g;
pg(y, 3, 4);              // ok
(y.*pg)(3, 4);            // error: "pg" is not a pointer to member function
std::invoke(pg, y, 3, 4); // ok

(C++23부터)

특수 멤버 함수 (Special member functions)

어떤 멤버 함수들은 특별해요. 특정 조건에서 사용자가 정의하지 않아도 컴파일러가 정의해요. 그 목록은 이래요.

  • 기본 생성자 (Default constructor)
  • 복사 생성자 (Copy constructor)
  • 이동 생성자 (Move constructor, C++11부터)
  • 복사 대입 연산자 (Copy assignment operator)
  • 이동 대입 연산자 (Move assignment operator, C++11부터)
  • 소멸자 (Destructor, C++20까지) / 예정 소멸자 (Prospective destructor, C++20부터)

특수 멤버 함수는 비교 연산자(C++20부터)와 후위 증가/감소 연산자(C++29부터)와 함께, defaulted 로 정의될 수 있는 — 즉 함수 본문 대신 = default로 정의될 수 있는 — 유일한 함수들이에요 (자세한 내용은 각각의 페이지를 봐요).

참고 (Notes)

기능 테스트 매크로: __cpp_ref_qualifiers(200710L, C++11, ref-qualifiers), __cpp_explicit_this_parameter(202110L, C++23, 명시적 객체 매개변수 / deducing this).

예제 (Example)

#include <exception>
#include <iostream>
#include <string>
#include <utility>

struct S
{
    int data;

    // simple converting constructor (declaration)
    S(int val);

    // simple explicit constructor (declaration)
    explicit S(std::string str);

    // const member function (definition)
    virtual int getData() const { return data; }
};

// definition of the constructor
S::S(int val) : data(val)
{
    std::cout << "ctor1 called, data = " << data << '\n';
}

// this constructor has a catch clause
S::S(std::string str) try : data(std::stoi(str))
{
    std::cout << "ctor2 called, data = " << data << '\n';
}
catch(const std::exception&)
{
    std::cout << "ctor2 failed, string was '" << str << "'\n";
    throw; // ctor's catch clause should always rethrow
}

struct D : S
{
    int data2;
    // constructor with a default argument
    D(int v1, int v2 = 11) : S(v1), data2(v2) {}

    // virtual member function
    int getData() const override { return data * data2; }

    // lvalue-only assignment operator
    D& operator=(D other) &
    {
        std::swap(other.data, data);
        std::swap(other.data2, data2);
        return *this;
    }
};

int main()
{
    D d1 = 1;
    S s2("2");

    try
    {
        S s3("not a number");
    }
    catch(const std::exception&) {}

    std::cout << s2.getData() << '\n';

    D d2(3, 4);
    d2 = d1;   // OK: assignment to lvalue
//  D(5) = d1; // ERROR: no suitable overload of operator=
}

출력:

ctor1 called, data = 1
ctor2 called, data = 2
ctor2 failed, string was 'not a number'
2
ctor1 called, data = 3

결함 보고 (Defect reports)

동작 변경 결함 보고로 비정적 멤버 함수가 둘러싼 클래스 이름과 같은 이름을 가질 수 있는지에 대한 모호함에 명시적 이름 제한이 추가됐어요 (CWG 194).

더 알아보기 (Learn more)