멤버 함수
멤버 함수 (Member functions)
비정적 멤버 함수(non-static member function) 는 static이나 friend 지정자 없이 클래스의 멤버 지정(member specification) 안에서 선언된 함수예요 (static과 friend의 효과는 각각 정적 멤버 함수와 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-한정자, final과 override 지정자(C++11부터), 멤버 이니셜라이저 목록이 포함돼요.
클래스 X의 비정적 멤버 함수는 이렇게 호출될 수 있어요.
X타입 객체에 클래스 멤버 접근 연산자를 사용해서X에서 파생된 클래스의 객체에X의 멤버 함수 본문 안에서 직접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, 또는 const와 volatile의 조합)로 선언될 수 있어요. 이 시퀀스는 함수 선언의 매개변수 목록 뒤에 와요. 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).