● 클래스에 숨겨진 기본적인 개념은 데이터 추상화와 캡슐화이다.
○ 데이터 추상화 : 인터페이스와 구현의 분리에 의존하는 프로그래밍(과 설계) 기법이다.
▷ 인터페이스 : 클래스 사용자가 실행할 수 있는 연산으로 구성
▷구현 : 클래스 데이터 멤버, 인터페이스를 구성하는 함수 본체. 일반적인 용도가 아닌 클래스를 정의하는 데 필요한 모든 함수를 포함한다.
○ 캡슐화 : 클래스 인터페이스와 구현의 분리를 강제한다. 캡슐화한 클래스에서는 그에 대한 구현을 숨기므로 클래스 사용자는 인터페이스를 사용할 수 있을 뿐 구현 내용에는 접근할 수 없다.
● 데이터 추상화와 캡슐화를 사용하는 클래스에서는 추상 데이터 타입을 정의한다.
7.1 추상 데이터 타입 정의하기
7.1.1 Sales_data 클래스 설계하기
● Sales_data에 대한 인터페이스는 다음 연산으로 구성한다.
○ 객체의 ISBN을 반환하는 isbn 멤버 함수
○ Sales_data 객체를 다른 객체에 더하는 combine 멤버 함수
○ 두 Sales_data 객체를 더하는 add 함수
○ istream에서 데이터를 읽어 Sales_data 객체에 넣는 read 함수
○ Sales_data 객체 값을 ostream에 출력하는 print 함수
개정판 Sales_data 클래스 사용하기
Sales_data total; // 처리 중인 합을 보관할 변수
if (read(cin, total)) { // 첫 번째 거래 내용을 읽는다.
Sales_data trans; // 다음 거래 내용을 담을 변수
while (read(cin, trans)) { // 남은 거래 내용을 읽는다.
if (total.isbn() == trans.isbn()) // isbn을 확인한다.
total.combine(trans); // 처리중인 total을 갱신한다.
else {
printf(cout, total) << endl; // 결과를 출력한다.
total = trans; // 다음 책을 처리한다.
}
}
print(cout, total) << endl; // 마지막 거래 내용을 출력한다.
}
else { // 입력이 없다
cerr << "NO, data?!" << endl; // 사용자에게 알린다.
}
7.1.2 개정판 Salse_data 클래스 정의하기
using namespace std;
struct Sales_data {
// 새로운 멤버 : Sales_data 객체에 대한 연산
string isbn() const { return bookNo; }
Sales_data& combine (const Sales_data&);
double avg_price() const;
// 데이터 멤버는 2.6.1절과 같다.
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// 비멤버 Sales_data 인터페이스 함수
Sales_data add(const Sales_data&, const Sales_data&);
ostream &print(ostream&, const Sales_data&);
istream &read(istream&, const Sales_data&):
● 클래스 안에서 정의한 함수는 암시적으로 inline(6.5.2절)이다.
멤버 함수 정의하기
● 멤버 함수 선언은 모두 클래스 안에서 해야하지만 멤버 함수 본체는 해당 클래스 본체 안 또는 밖에서 정의할 수 있다.
○ Sales_data에서 isbn은 클래스 안에서 정의하지만 combine과 avg_price는 다른 곳에서 정의한다.
● 멤버 함수 본체 역시 구역이다.
this에 대한 소개
● 멤버 함수에서는 this라는 특별한 암시적 매개변수를 통해 자신을 호출하는 객체에 접근한다.
○ 멤버 함수를 호출하면 그 함수를 호출하는 객체 주소로 this를 초기화한다.
○ ex)
total.isbn()
을 호출하면 컴파일러에서는 total의 주소를 isbn의 암시적 this 매개변수에 전달한다.
즉 isbn에서 bookNo를 사용할 때는 this로 가리키는 멤버를 암시적으로 사용하는 것이다.
이는 마치 this -> bookNo로 쓴 것과 같다
● this 매개변수는 암시적으로 정의한다.
● this에서는 항상 객체 '자신'을 참조하므로 this는 const 포인터이다.
const 멤버 함수에 대한 소개
● isbn 함수에 대한 다른 중요한 부분은 매개변수 목록 다음에 있는 const 키워드이다. 이 const의 목적은 암시적 this 포인터의 타입을 변경하는 것이다.
○ 비 - 'const' 멤버 함수에서 'this' 포인터는 'Type* const'이다. 이는 'this' 포인터가 가리키는 객체의 상태를 변경할 수 있음을 의미한다.
○ 'const' 멤버 함수에서 'this' 포인터는 'const Type* const'입니다. 이는 'this' 포인터가 가리키는 객체의 상태를 변경할 수 없음을 의미합니다.
● isbn 본체를 마치 다음처럼 생각할 수 있다.
// 암시적 this 포인터를 사용하는 방법을 보여주는 의사 코드 예
// 이 코드는 위법이다. : this 포인터는 명시적으로 직접 정의할 수 없다.
// isbn이 const 멤버이므로 this는 const에 대한 포인터임에 주의한다.
std::string Sales_data::isbn(const Sales_data *const this) {
return this -> isbn;
}
● const 객체에 대한 참조자 또는 포인터와 const인 객체에서는 const 멤버 함수만 호출 할 수 있다.
클래스 유효 범위와 멤버 함수
● 클래스 멤버 함수 정의는 클래스 유효 범위 내에 중첩된다. 그러므로 isbn에서 bookNo라는 이름을 사용하면 Sales_data안에 정의한 데이터 멤버로 해석한다.
● 멤버 함수 본체에서는 해당 클래스의 다른 멤버가 클래스 내 어디에 있든 관계없이 그 멤버를 사용할 수 있다.
클래스 밖에서 멤버 함수 정의하기
● 클래스 본체 밖에서 멤버 함수를 정읳라 때는 멤버 정의와 선언이 일치해야 한다.
○ 즉 반환 타입, 매개변수 목록, 이름이 크래스 본체 내 선언과 일치해야 한다.
○ 멤버를 const 멤버 함수로 선언하면 정의에도 매개변수 목록 다음에 const를 지정해야 한다.
double Sales_data::avg_price() const {
if (units_sold)
return revenue/units_sold;
else
return 0;
}
객체 '자신'을 반환하는 함수 정의하기
● combine 함수는 복합 대입 연산자 += 처럼 행동한다. 이 함수를 호출하는 객체는 대입의 왼쪽 피연산자를 나타내고, 오른쪽 피연산자는 명시적 인자로 전달한다.
Sales_data& Sales_data::combine(cosnt Sales_data &rhs) {
units_sold += rhs.units_sold; // rhs의 멤버를
revenue += rhs.revenue; // 객체 '자신'의 멤버에 더한다
return *this; // 이 함수를 호출한 객체를 반환한다.
}
7.1.3 클래스와 관련 있는 비멤버 함수 정의하기
● 비멤버 함수는 일반적으로 함수 선언과 정의를 분리하고 개념적으로 클래스에 속하지만 그 클래스 안에서 정의하지 않는 함수는 일반적으로 그 클래스와 같은 헤더에 선언한다.
● 보통 클래스 인터페이스에 속하는 비멤버 함수는 해당 클래스와 같은 헤더에 선언하는 것이 좋다.
read와 print 함수 정의하기
● read 함수에서는 지정한 스트림에서 데이터를 읽어 지정한 객체에 넣는다. print 함수에서는 지정한 객체의 내용을 지정한 스트림으로 출력한다.
● 눈여겨볼 만한 내용 두가지
○ 1. read와 print 모두 각 IO 클래스 타입에 대한 참조자를 취한다. const가 아닌 보통의 참조자를 취한다.
○ 2. print에서 줄바꿈을 출력하지 않음에 주의한다.
add 함수 정의하기
● add 함수에서는 두 Sales_data 객체를 취하고 두 객체의 합을 나타내는 새로운 Sales_data를 반환한다.
7.1.4 생성자
● 클래스에서는 생성자라고 하는 특별한 멤버 함수를 하나 이상 정의해 객체 초기화를 제어한다.
● 생성자에서는 클래스 객체의 데이터 멤버를 초기화한다.
● 생성자는 클래스 타입 객체를 생성할 때마다 실행한다.
● 생성자는 클래스와 이름이 같다. 다른 함수와 달리 생성자에는 반환 타입이 없지만, 다른 함수처럼 (비어 있을 수 있는) 매개변수 목록과 (비어 있을 수 있는) 함수 본체는 있다.
● 클래스에는 생성자가 여럿일 수 있다. 다중 정의한 함수처럼 생성자 역시 매개변수 수나 타입이 다른 생성자와 서로 달라야 한다.
● 생성자에서는 생성 중에 const 객체에 기록할 수 있다.
함성 기본 생성자
● 클래스에서는 기본 생성자라는 특별한 생성자를 정의해 기본 초기화를 제어한다. 기본 생성자에서는 인자를 취하지 않는다.
● 클래스에서 생성자를 명시적으로 정의하지 않으면 컴파일러에서 기본 생성자를 암시적으로 정의한다는 것이다.
● 컴파일러에서 만들어 낸 생성자를 합성 기본 생성자라 한다. 클래스 대부분에서 이 합성 생성자는 해당 클래스의 각 데이터 멤버를 다음처럼 초기화한다.
○ 클래스 내 초기 값이 있으면 이를 사용해 해당 멤버를 초기화한다.
○ 그렇지 않는 멤버는 기본 초기화한다.
일부 클래스에서는 합성 기본 생성자에 의존할 수 없다.
● 기본 생성자를 정의하는 이유 :
○ 1. 어떤 생상자도 정의하지 않을 때에만 컴파일러에서 기본 생성자를 만들기 때문이다.
○ 2. 일부 클래스의 경우 합성 기본 생성자에서 잘못 처리하기 때문이다.
○ 3. 컴파일러에서 합성 생성자를 만들 수 없기 때문이다.
● 컴파일러에서는 클래스에서 어떤 생성자도 선언하지 않을 때에만 기본 생성자를 자동으로 생성하다.
● 클래스에 내장 또는 복합 타입 멤버가 있으면 일반적으로 그러한 모든 멤버에 클래스 내 초기 값을 지정했을 때에만 합성 기본 생성자에 의존하다.
Sales_data 생성자 정의하기
● Sales_data 클래스에서는 다음 매개변수를 사용해 네 가지 생성자를 정의한다.
○ 거래 내용을 읽어 올 istream&
○ ISBN을 나타내는 const string&, 판매 부수를 나타내는 unsigned, 판매 가격을 나타내는 double
○ ISBN을 나타내는 const string&. 이 생성자에서는 다른 멤버에 기본 값을 사용한다.
○ 방금 본 것처럼 다른 생성자를 정의했기 때문에 반드시 정의해야 하는 빈 매개변수 목록(즉 기본 생성자)
strcut Sales_data {
// 추가한 생성자
Sales_data () = default;
Sales_data (const std::string &s): bookNo(s) { }
Sales_data (const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) {}
Sales_data (std::istream &);
// 이전과 같은 다른 멤버
std::string isbn() const { return bookNo; }
Sales_data& combine(const Sales_data&);
daouble avg_price() const;
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
= default가 뜻하는 것
Sales_data() = default;
● 먼저 이 생성자는 인자를 취하지 않으므로 기본 생성자를 정의하는 것에 주의한다.
● 새로운 표준에서는 기본 행동을 바랄 때 매개변수 목록 다음에 = default를 추가해 컴파일러에서 그 생성자를 만들어 내도록 요청할 수 있다.
● = default는 클래스 본체 안 선언이나 클래스 본체 밖 정의 모두에 사용할 수 있다.
● 다른 함수처럼 = default를 클래스 본체 안에서 사용하면 기본 생성자는 인라인을 하고, 클래스 밖 정의에서 사용하면 그 멤버는 기본적으로 인라인하지 않는다.
● Sales_data에서 기본 생성자는 내장 타입 데이터 멤버에 초기 값을 지정하기 때문에 제대로 작동한다. 컴파일러에서 클래스 내 초기 값을 지원하지 않으면 기본 생성자에서는 (바로 다음에 설명할) 생성자 초기 값 목록을 사용해 클래스의 모든 멤버를 초기화해야 한다.
생성자 초기 값 목록
Sales_data (const std::string &s) : bookNo(s) {}
Sales_data (const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) {}
● 생성자 초기 값 목록은 콜론, 콜론과 (빈) 함수 본체를 정의하고 있는 중괄호 사이에 있는 코드이다.
● 생성할 객체의 데이터 멤버 하나 이상에 사용할 초기 값을 지정한다.
● 생성자 초기 값 목록은 멤버 이름 목록이며 각 이름 다음에는 (중괄호 안 또는) 괄호에 넣은, 해당 멤버의 초기 값이 온다.
● 여러 멤버에 대한 초기화는 쉼표로 구분한다.
● 다른 초기 값을 사용하는 경우가 아니라면 생성자에서는 클래스 내 초기 값을 재지정하지 않는 것이 좋다. 클래스 내 초기 값으 사용할 수 없으면 각 생성자에서는 내장 타입인 모든 멤버를 명시적으로 초기화해야 한다.
클래스 본체 밖에서 생성자 정의하기
#include <iostream>
#include <string>
// 클래스 선언부
class Person {
private:
std::string name;
int age;
public:
// 생성자의 프로토타입
Person(const std::string& name, int age);
void display() const;
};
// 클래스 본체 밖에서 생성자 정의
Person::Person(const std::string& name, int age) : name(name), age(age) {}
// 클래스 본체 밖에서 멤버 함수 정의
void Person::display() const {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
int main() {
Person person("John Doe", 30);
person.display();
return 0;
}
7.1.5 복사, 대입, 소멸
● 변수를 초기화 할 때 또는 값으로 객체를 전달하거나 반환 할 때 처럼 여러 상황에서 객체를 복사한다.
● 대입 연산자를 사용할 때는 객체를 대입하고, 구역을 빠져나갈 때 해당 구역에서 생성한 지역 객체가 소멸하는 것처럼 존재하기를 멈출 떄 객체는 소멸한다.
일부 클래스에서는 합성 버전에 의존할 수 없다
7.2 접근 제어와 캡슐화
● C++에서는 접근 지정자를 사용해 캡슐화를 강제한다.
○ public 지정자 다음에 정의한 멤버는 프로그램의 모든 부분에서 접근할 수 있다. public 멤버는 해당 클래스 인터페이스를 정의한다.
○ private 지정자 다음에 정의한 멤버는 클래스 멤버 함수에서 접근할 수 있지만 해당 클래스를 사용하는 코드에서는 접근할 수 없다. private 구역은 구현을 캡슐화한다(즉 숨긴다).
// Sales_data를 다시 한번 재정의하면 다음과 같다.
using namespaces std;
class Sales_data {
public:
Sales_data() = default;
Sales_data (const string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) {}
Sales_data (const string &s): bookNo(s) {}
Sales_data(istream&);
string isbn() const {return bookNo;}
Sales_data &combine (combine Sales_data&);
private:
double avg_price() const
{ return units_sold ? revenue/units_sold : 0; }
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
}
class나 struct 키워드 사용하기
● 클래스에서는 첫 번째 접근 지정자 이전에 멤버를 정의할 수 있다. 그런 멤버에는 클래스를 정의한 방법에 따라 접근 여부가 달라진다. struct 키워드를 사용하면 첫 번째 접근 지정자 이전에 정의한 멤버는 public이고 class를 사용하면 그 멤버는 private 이다.
● 클래스 정의할 때 class를 사용하는 것과 struct를 사용하는 것의 유일한 차이는 기본 접근 수준이다.
7.2.1 프렌드
● 클래스에서는 다른 클래스나 함수를 프렌드로 지정해 자신의 public이 아닌 멤버를 그 클래스나 함수에서 접근할 수 있도록 한다.
● 클래스에서 함수를 프렌드로 만들 때는 friend 키워드를 앞에 붙인 해당 함수에 대한 선언을 포함한다.
// Sales_data를 다시 한번 재정의하면 다음과 같다.
using namespaces std;
class Sales_data {
// 비멤버 Sales_data 연산에 대해 추가한 프렌드 선언
friend Sales_data add(const Sales_data&, const Sales_data&);
friend istream &read(istream&, Sales_data&):
friend ostream &print(ostream&, const Sales_data&);
// 다른 멤버와 접근 지정자는 이전과 같다.
public:
Sales_data() = default;
Sales_data (const string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) {}
Sales_data (const string &s): bookNo(s) {}
Sales_data(istream&);
string isbn() const {return bookNo;}friend Sales_data add(const Sales_data&, const Sales_data&);
friend istream &read(istream&, Sales_data&):
friend ostream &print(ostream&, const Sales_data&);
Sales_data &combine (combine Sales_data&);
private:
double avg_price() const
{ return units_sold ? revenue/units_sold : 0; }
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
}
// Sales_data 인터페이스 중 비멤버 부분에 대한 선언
Sales_data add(const Sales_data&, const Sales_data&);
istream &read(istream&, Sales_data&):
ostream &print(ostream&, const Sales_data&);
● 캡슐화의 이점
○ 사용자 코드에서는 캡슐화한 객체 상태를 무심코 망가뜨릴 수 없다.
○ 시간이 흘러도 사용자 수준 코드를 변경하지 않고 캡슐화한 클래스의 구현 내용을 변경할 수 있다.
프렌드 선언
7.3 추가적인 클래스 기능
7.3.1 클래스 멤버 다시 보기
class Screen {
public:
typedef std::string::size_type pos;
public:
pos cursor = 0;
pos height - 0, width = 0;
std::string contents;
};
● pos 선언에 주목할 내용은 두 가지이다.
○ 1. typedef를 사용했지만 타입 별칭을 사용해 동일하게 할 수 있다.
○ 2. 일반적인 멤버와 달리 타입을 정의하는 멤버는 사용하기 전에 선언해야 한다.
Screen 클래스 멤버 함수
class Screen {
public:
typedef std::string::size_type pos;
Screen() = default; // Screen에 다른 생성자가 있으므로 필요하다
// curses는 클래스 내 초기 값인 0으로 초기화한다.
Screen (pos ht, pos wd, char c): height(ht), width(wd), contents(ht*wd, c) {}
char get() const // cursor 위치에서 문자를 얻는다.
{ return constents[cursor]; } // 암시적 inline
inline char get(pos ht, pos wd) const; // 명시적 inline
Screen &move(pos r, pos c); // 나중에 inline으로 만들 수 있다
public:
pos cursor = 0;
pos height - 0, width = 0;
std::string contents;
};
멤버를 inline으로 만들기
● 클래스 본체 안과 밖 모두 함수 정의에 inline을 지정할 수 있다.
멤버 함수 다중 정의하기
Screen myscreen;
char ch = myscreen.get(); // Screen::get()를 호출한다.
ch = myscreen.get(0,0); // Screen::get(pos, pos)를 호출한다.
mutable 멤버 함수
● (매우 흔하지 않지만) 때로는 어떤 데이터 멤버를 const 멤버 함수 안에서조차 변경하길 바랄 수 있다. 이런 멤버는 선언에 mutable 키워드를 사용해 나타낸다.
클래스 타입 데이터 멤버에 대한 초기값
class Window_mgr {
private:
// 이 Window_mgr에서 추적하고 있는 여러 Screen
// 기본적으로 Window_mgr에는 표준 크기의 공백 Screen이 하나 있다
std::vector<Screen> screens{Screen(24, 80, ' ')};
};
● vector 멤버를 목록 초기화 함
● 초기 값은 반드시 = 초기화 형식이나 중괄호를 사용하는 직접 초기화 형식 중 하나를 사용해야한다.
7.3.2 *this를 반환하는 함수
● 객체 복사본이 아닌 객체 자체를 반환함을 뜻한다.
const 멤버 함수에서 *this 반환하기
● *this를 참조자로 반환하는 const 멤버는 반환 타입이 const에 대한 참조여야 한다.
const에 기반한 다중 정의
● 포인터 매개변수가 const를 가리키는지 여부에 따라 함수를 다중 정의할 수 있는 것고 같은 이유로 멤버 함수가 const인지 여부에 다라 다중 정의할 수 있다.
7.3.3 클래스 타입
● 모든 클래스에서는 유일한 타입을 정의한다. 서로 다른 두 클래스는 같은 멤버를 정의하더라도 서로 다른 두타입을 정의한다.
● 두 클래스에서 멤버 목록이 정확히 같더라도 그 둘은 서로 다른 타입이다. 각 클래스의 멤버는 다른 클래스(또는 다른 유효 범위)의 멤버와 구별된다.
클래스 선언
● 전방 선언 : 주로 클래스나 함수의 이름을 미리 알려주기 위해 사용된다.
○ 선언 이후 정의가 나타나기 전까지는 불완전 타입이다. 즉 클래스 타입임을 알고 있지만 그 타입에서 담고 있는 멤버가 무엇인지는 모른다.
7.3.4 프렌드 관계 다시보기
클래스 사이의 프렌드 관계
● 프렌드 관계는 전이하지 않음을 이해해야 한다.
● 어느 클래스 또는 함수가 자신의 프랜드인지는 각 클래스에서 제어한다.
멤버 함수를 프렌드로 만들기
● 멤버 함수를 프렌드로 만들 때는 선언과 정의 사이에 상호 의존성을 조화시키며 프로그램을 주의 깊게 구조화해야 한다.
다중 정의한 함수와 프렌드 관계
● 클래스에서는 프렌드로 만들고 싶은 다중 정의한 함수 집합의 각 함수를 프렌드로 선언해야 한다.
프렌드 선언과 유효 범위
● 프렌드 선언에서 이름이 처음 나타나면 그 이름은, 둘러싸고 있는 유효 범위에 속하는 것으로 암시적으로 가정한다.
7.4 클래스 유효 범위
유효 범위와 클래스 밖에서 정의한 멤버
● 클래스 이름이 나타나면 매개변수 목록과 함수 본체를 포함한 나머지 정의 부분은 그 클래스 유효 범위에 속한다. 결과적으로 한정하지 않고 다른 클래스 멤버를 참조할 수 있다.
● 힘수 반환 타입은 일반적으로 함수 이름 앞에 나타난다. 멤버 함수를 클래스 본체 밖에서 정의할 때 반환 타입에서 사용한 모든 이름은 클래스 유효 범위 밖이다. 그러므로 반환 타입에는 해당 멤버가 속한 클래스를 지정해야 한다.
7.4.1 이름 검색과 클래스 유효 범위
● 이름 검색
○ 먼저 이름을 사용한 구역 안에서 해당 이름의 선언을 찾는다. 사용 이전에 선언한 이름만 고려한다.
○ 이름을 찾지 못하면 이를 둘러싸고 있는 유효 범위에서 찾는다.
○ 선언을 찾지 못하면 프로그램에서 오류를 발생한다.
● 클래스 안에서 정의한 멤버 함수 안에서 이름을 분석하는 방식은 이러한 이름 검색 규칙과는 다르게 행동하는 것처럼 보인다. 하지만 이 경우 곁으로 보이는 것에 속고 있는 것이다. 클래스 정의는 두 단계로 처리한다.
○ 먼저 멤버 선언을 컴파일한다.
○ 클래스 전체가 나타난 후에야 함수 본체를 컴파일한다.
● 컴파일러에서 해당 클래스 내 모든 선언을 처리한 '후에' 멤버 함수 정의를 처리한다.
클래스 멤버 선언에 대한 이름 검색
● 반환 타입과 매개변수 목록에서 사용한 타입에서 사용한 이름을 포함해 선언에서 사용한 이름은 사용하기 전에 나타나야 한다. ● 해당 클래스 내에서 아직 나타나지 않는 이름을 멤버 선언에서 사용하면 컴파일러에서는 그 클래스를 정의한 유효 범위 안에서 그 이름을 찾는다.
타입 이름은 특별하다
● 일반적으로 외부 유효 범위에 속한 이름을 내부 유효 범위에서 이미 사용하고 있더라도 해당 내부 유효 범위에서는 그 이름을 재정의할 수 있다.
● 클래스 에서는, 외부 유효 범위에 속한 이름을 멤버에서 사용하고 그 이름이 타입이면 그 클래스에서 그 이름을 재정의 할 수 없다.
멤버 정의 내 일반적인 구역 유효 범위 이름 검색
● 클래스 멤버가 가려지더라도 해당 클래스 이름으로 그 멤버 이름을 한정하거나 this 포인터를 명시적으로 사용하면 여전히 사용할 수 있다.
● 일단 킵
클래스 유효 범위를 찾은 후, 둘러싸고 있는 유효 범위에서 찾는다
● 컴파일러에서 해당 이름을 함수나 클래스 유효 범위에서 찾지 못하면 이를 둘러싸고 있는 유효 범위에서 그 이름을 찾는다.
● 외부 객체가 가려지더라도 범위 지정 연산자를 사용해 여전히 그 객체에 접근할 수 있다.
이름은 파일 내에서 나타나는 위치로 분석한다.
● 멤버를 해당 클래스 밖에서 정의했을 때, 이름 검색 세 번째 단계에서는 그 멤버 정의의 유효 범위에서 선언한 이름과 클래스 정의의 유효 범위 안에 나타나는 이름을 모두 포함한다.
7.5 생성자 다시 보기
7.5.1 생성자 초기 값 목록
● 생성자 초기 값 목록에서 멤버를 명시적으로 초기화하지 않으면 그 멤버는 생성자 본체를 실행하기 전에 기본 초기화한다.
때로는 생성자 초기 값이 필요하다
● 항상은 아니지만 종종 멤버 초기화와 대입 사이의 차이는 무시할 수 있다.
● const나 참조자인 멤버는 반드시 초기화해야 한다. 마찬가지로 기본 생성자를 정의하지 않은 클래스 타입 멤버 역시 반드시 초기화 해야한다.
● const, 참조자, 기본 생성자가 없는 클래스 타입인 멤버에 값을 지정하려면 '반드시
생성자 초기 값 목록을 사용해야 한다.
● 생성자 초기 값을 사용한다.
멤버 초기화 순서
● 멤버는 클래스 정의에 나타나는 순서로 초기화한다.
● 생성자 초기화식을 멤버 선언과 같은 순서로 만드는 것이 좋다. 게다가 가능하면 멤버를 사용해 다른 멤버를 초기화하는 것은 피한다.
기본 인자와 생성자
● 모든 매개변수에 대해 기본 인자를 지정한 생성자는 기본 생성자도 정의한다.
7.5.2 위임 생성자
● 위임 생성자에서는 자신의 클래스에 있는 다른 생성자를 사용해 초기화를 수행할 수 있다. 즉 자신의 일 중 일부(또는 전부)를 다른 생성자에 '위임'한다.
● 문법
class MyClass {
public:
MyClass(int param1) {
// 초기화 작업
}
MyClass(double param2) : MyClass(42) { // 다른 생성자 호출
// 추가 초기화 작업
}
};
7.5.3 기본 생서자의 역할
● 기본 초기화는 다음과 같은 경우에 일어난다.
○ 초기 값 없이 구역에서 static이 아닌 변수나 배열을 정의할 때
○ 클래스 타입 멤버가 있는 클래스에서 합성 기본 생성자를 사용할 때
○ 클래스 타입 멤버를 생성자 초기 값 목록에서 명시적으로 초기화하지 않을 때
● 값 초기화
○ 배열을 초기화하는 동안 해당 배열 크기보다 더 적은 초기 값을 지정할 때
○ 초기 값 없이 지역 static 객체를 정의할 때
○ T가 타입 이름일 때 T()를 형성하는 표현식을 만들어 명시적으로 값 초기화를 요청할 때
기본 생성자 사용하기
● 초기화에 기본 생성자를 사용하는 객체를정의하는 올바른 방법은 뒤에 오는 빈 괄호를 버리는 것이다.
7.5.4 암시적 클래스 타입 변환
● 변환 생성자 : 인자 하나로 호출할 수 있는 모든 생성자에서는 클래스 타입으로 암시적 변환을 정의하는데 이런 생성자를 변환 생성자라고 한다.
● 클래스 타입 변환은 오직 하나만 허용한다.
● 클래스 타입 변환은 항상 유효하지는 않다.
생성자에서 정의한 암시적 변환 막기
● 생성자를 explicit로 선언하면 암시적 변환이 필요한 상황에서 생성자를사용하지 못하게 할 수 있다.
● explicit 키워드는 인자 하나로 호출할 수 있는 생성자에만 의미가 있다.
● explicit 키워드는 클래스 안에서 선언한 생성자에만 사용하며 클래스 본체 밖에서 만든 정의에는 반복하지 않는다.
● explicit 생성자는 직접 초기화에만 사용할 수 있다.
변환에 생성자를 명시적으로 사용하기
● 컴파일러에서는 암시적 변환에 explicit 생성자를 사용하지 않더라도 이런 생성자를 사용해 명시적으로 변환을 강제할 수 있다.
explicit 생성자가 있느 라이브러리 클래스
● const char* 타입 매개변수 하나를 취하는 string 생성자는 explicit가 아니다.
● 크기를 취하는 vector 생성자는 explicit이다.
7.5.5 집합 클래스
● 집합 클래스를 사용하면 사용자가 해당 클래스 멤버에 직접 접근할 수 있으며, 이 클래스는 초기화 구문도 특별하다. 만약
○ 모든 데이터 멤버가 public이고
○ 생성자를 전혀 정의하지 않으며
○ 클래스 내 초기 값이 없고
○ 기초 클래스나 virtual 함수가 없으면
클래스는 집합체이다.
○ ex)
struct Data {
int ival;
string s;
}
● 집합 클래스의 데이터 멤버는 중괄호로 둘러싼 멤버 초기 값 목록으로 초기화 할 수 있다.
● 클래스 타입 객체의 멤버를 명시적으로 초기화할 때 3가지 단점
○ 클래스의 모든 데이터 멤버는 public이어야 한다.
○ 모든 객체의 모든 멤버를 올바르게 초기화하기 위해 크래스 사용자에게 짐을 지운다. 초기 값을 잊거나 적절하지 않은 초기 값을 지정하기 쉬워 이러한 초기화는 장황하고 오류가 생기기 쉽다.
○ 멤버를 추가하거나 삭제할 때 모든 초기화를 갱신해야 한다.
7.5.6 상수 클래스
● 모든 데이터 멤버가 상수 타입인 집합 크래스는 상수 클래스이다. 다음 조건에 맞는 비집합 클래스 또한 상수 클래스이다.
○ 모든 데이터 멤버는 상수 타입이어야한다.
○ 클래스에 constexpr 생성자가 적어도 하나 있어야 한다.
○ 데이터 멤버에 클래스 내 초기 값이 있을 때, 내장 타입 멤버에 대한 초기 값은 반드시 상수 표현식이어야 하고 멤버가 클래스 타입이면 초기 값은 반드시 해당 멤버 자신의 constexper 생성자를 사용해야 한다.
○ 클래스에서 소멸자는 반드시 기본 정의를 사용해야 한다. 소멸자는 해당 클래스 타입 객체를 소멸하는 멤버이다.
constexpr 생성자
● 상수 클래스에서는 constexpr 생성자가 적 어도 하나 있어야한다.
● constexpr 생성자는 = default로 선언할 수 있다. 그렇지 않으면 constexpr 생성자는 return 문이 없어야 한다.
● constexpr 생성자에서는 반드시 모든 데이터 멤버를 초기화해야 한다.
● constexpr 생성자는 constexpr인 객체를 생성할 때 또는 constexpr 함수의 매개 변수와 반환 타입을 사용한다.
7.6 static 클래스 멤버
static 멤버 선언하기
● static 멤버는 모든 객체 외부에 존재한다. 객체에는 static 데이터 멤버와 연관된 데이터가 없다.
● static 멤버 함수 역시 어느 객체와도 결합하지 않는다. 그러므로 이런 함수에는 this 포인터가 없다. 결과적으로 static 멤버 함수는 const로 선언할 수 없고 static 멤버 본체에서 this를 참조할 수 없다.
클래스 static 멤버 사용하기
● static 멤버가 해당 클래스 객체의 일부분은 아니지만 해당 클래스 타입인 객체, 참조자, 포인터를 사용해 static 멤버에 접근할 수 있다.
● 멤버 함수에서는 범위 연산자 없이 static 멤버를 직접 사용할 수 있다.
static 멤버 정의하기
● 다른 멤버 함수와 마찬가지로 static 멤버 함수는 클래스 본체 안 또는 밖에서 정의할 수 있다.
● static 멤버를 클래스 밖에서 정의할 때는 static 키워드를 반복하지 않는다. 이 키워드는 클래스 본체 안에서 선언할 때 만 사용한다.
● 다른 클래스 멤버처럼 클래스 본체 밖에서 클래스 static 멤버를 참조할 때는 그 멤버를 정의한 클래스를 지정해야 한다. 하지만 static 키워드는 클래스 본체 안에서 선언할 때만 사용한다.
● static 데이터 멤버는 해당 클래스 타입의 개별 객체에 속하지 않으므로 해당 클래스 객체를 생성할 때 정의하지 않는다.
static 데이터 멤버의 클래스 내 초기화
● 일반적으로 클래스 static 멤버는 클래스 본체 안에서 초기화할 수 없다.
● const 정수 타입인 static 멤버에는 클래스 내 초기 값을 지정할 수 있으며 상수 타입 constexpr인 static 멤버에는 반드시 클래스 내 초기 값을 사용해야한다.
● 초기 값은 반드시 상수 표현식이어야 한다.
static 멤버는 보통의 멤버로 할 수 없는 방식으로 사용할 수 있다.
● static 데이터 멤버의 타입은 자신이 멤버로 속한 클래스의 타입과 같을 수 있다.
● static이 아닌 데이터 멤버는 해당 클래스 객체에 대한 포인터나 참조자로만 선언할 수 있다.
● static과 보통의 멤버 사이의 또 다른 차이는 static 멤버를 기본 인자로 사용할 수 있다는 점이다.
'C++ Primer' 카테고리의 다른 글
함수 (0) | 2024.07.02 |
---|---|
문장 (0) | 2024.06.29 |
표현식 (0) | 2024.06.26 |
문자열, 벡터와 배열 (0) | 2024.06.26 |
변수와 기본 타입 (0) | 2024.06.25 |