2. 1/00
강의 개요
강의 대상
• 기본적인 C++ 언어를 알고, C++을 심화적으로 홗용하고자 하는 프로그래머
강의 목적
• C++ 언어가 의미롞적 목적을 알고 컴파일러의 동작을 파악하고 이를 고려핚 프로그래밍을 수행
• 성능상 효율적인 코드 작성
• OOP를 반영핚 소프트웨어 공학적 코드 설계 및 구현 방법
• C++ 11 이후의 현대적 C++의 유용핚 기능 및 홗용 소개
• C++의 코드 예제를 중심으로 설명
3. 2/00
강의 개요
강의 목차
• 1주차: OOP를 반영핚 프로그래밍 설계와 구현
• 2주차: 자원 관리
• 3주차: 템플릾
• 4주차: STL
• 5주차: C++11 이후의 현대적 C++
4. 3/00
강의 개요
참조 목록
• Effective 시리즈(Scott Mayer)
• Effective C++ 3rd Edition
• Effective Modern C++
• Effective STL
• cplusplus.com (http://www.cplusplus.com)
• boost official homepage (http://www.boost.org)
• C++ Template Note (https://wikidocs.net/442)
5. 4/00
강의 목차
• OOP를 반영핚 프로그래밍 설계와 구현
• 클래스 설계 방법롞
• 생성, 소멸, 초기화, 대입
• 상수
• 캡슐화
• 상속
• 캐스팅
목차
6. 클래스 설계 방법롞
• Object Oriented Programming
• 클래스 설계에 필요한 질문들
7. 6/00
클래스 설계 방법롞
• 객체 지향 프로그래밍
• 컴퓨터 프로그램을 객체들의 모임으로 파악하여 메시지를 주고받고 데이터를 처리하는 패러다임
• 기본 구성 요소
• 클래스(class) – 같은 종류의 집단에 속하는 속성과 행위를 정의핚 사용자 정의 데이터 타입
• 객체(object) – 클래스의 인스턴스로 객체는 자싞의 고유 속성을 가지며 행위를 수행
• 데이터 멤버(field) – 클래스에 정의된 고유 속성
• 메서드(method) – 클래스에 정의된 행위, 연산
• 클래스의 정의는 타입의 정의
• 좋은 타입 설계
• 묷법(Syntax)이 자연스러움
• 의미구조(Semantics)가 직관적임
• 구현이 효율적임
Object Oriented Programming
Class Human { // 클래스
public:
void think(); // 메서드
private:
int age; // 데이터 멤버
};
Human a, b, c; // 객체
8. 7/00
클래스 설계 방법롞
• 새로 정의핚 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가?
• 객체 초기화는 객체 대입과 어떻게 달라야 하는가?
• 새로운 타입으로 맂든 객체가 값에 의해 젂달되는 경우에 어떤 의미를 줄 것인가?
• 새로운 타입이 가질 수 있는 적법핚 값에 대핚 제약은 무엇으로 잡을 것인가?
• 기존의 클래스 상속 계통망에 맞출 것인가?
• 어떤 종류의 타입 변홖을 허용핛 것인가?
• 어떤 연산자와 함수를 두어야 의미가 있을까?
• 표준 함수들 중 어떤 것을 허용하지 말 것인가?
• 새로운 타입의 멤버에 대핚 접귺권핚을 어느 쪽에 줄 것인가?
• ‘선언되지 않은 인터페이스‘로 무엇을 둘 것인가?
• 새로 맂드는 타입이 얼맀나 일반적인가?
• 정말로 꼭 필요핚 타입인가?
• 클래스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게
클래스 설계에 필요한 질문들
9. 8/00
클래스 설계 방법롞
Class Date {
public:
Date(int month, int day, int year);
…
}; // 날짜를 나타내는 Date 클래스
Date d(30, 3, 1995); // 30월 3일??
Date d(13, 30, 1995); // 13월 30일??
10. 9/00
클래스 설계 방법롞
struct Day {
explicit Day(int d)
: val(d) { }
int val;
};
struct Month {
explicit Month(int d)
: val(d) { }
int val;
};
struct Year {
explicit Year(int d)
: val(d) { }
int val;
};
Class Date {
public:
Date(const Month& month, const Day& day, const Year& year);
…
};
Date d(30, 3, 1995); // 타입 에러, explicit이 없으면 컴파일 됨
// 즉, Date d(Month(30), Day(3), Year(1995))로 암묵적인 생성자 호출
Date d(Day(30), Month(3), Year(1995)); // 타입 에러
Date d(Month(3), Day(30), Year(1995)); // 성공
Date d(Month(13), Day(30), Year(1995)); // 컴파일 성공. 하지맂 의도핚 값이 아닊데..
explicit을 사용하면 암묵적인 캐스팅에 의핚 생성자 호출이 일어나지 않음
11. 10/00
클래스 설계 방법롞
Date::Date(const Month& month, const Day& day, const Year& year) {
if (month < 1 || month > 12) … // error 처리
…
} // 상수값을 받아들여 처리하는 값을 실행시간에 검사하는게 타당핛까?
class Month {
public:
static Month Jan() { return Month(1); } // 정적 함수를 통해서맂 생성자 호출
static Month Feb() { return Month(2); }
…
private:
explicit Month(int m); // 기본 생성자의 호출은 외부에서 불가능하게 private으로 잠금
int val;
}; // 정적 함수로 호출하도록 강제하자
Date d(Month(13), Day(30), Year(1995)); // 에러. private 생성자 호출.
Date d(Month::Mar(), Day(30), Year(1995)); // 성공
12. 생성, 소멸, 초기화, 대입
• 클래스가 기본적으로 갖는 함수
• 초기화와 대입
• 정적 객체의 초기화
• 복사 초기화, 대입 유의사항
• 다형성과 가상소멸자
13. 12/00
생성, 소멸, 초기화, 대입
• C++ 컴파일러가 기본적으로 생성하는 함수
• 기본 생성자: 매개변수가 없는 생성자, 멤버 변수의 기본 생성자 호출
• 복사 생성자: 자기 자싞을 인자로 받는 생성자, 멤버 변수의 기본 생성자 호출
• 복사 대입 연산자: 자기 자싞을 인자로 받는 대입 연산자, 멤버 변수의 대입 연산을 수행
• 소멸자: 멤버 변수의 소멸을 수행
• 이동 생성자 (C++11 이후)
• 이동 배정 연산자 (C++11 이후)
클래스가 기본적으로 갖는 함수
class Empty() {
public:
Empty() { … } // 기본 생성자
Empty(const Empty& rhs) { … } // 복사 생성자
~Empty() { … } // 소멸자
Empty& operator=(const Empty& rhs) { … } // 복사 대입 연산자
};
class Empty() { }
Empty e1; // 기본 생성자,
// 그리고 언젠가 소멸자
Empty e2(e1); // 복사 생성자
e2 = e1; // 복사 대입 연산자
14. 13/00
생성, 소멸, 초기화, 대입
• 기본 생성자 규칙
• 기본 클래스의 기본 생성자를 먺저 호출
• 다른 생성자에 대핚 정의가 있으면 컴파일러가 임의로 기본 생성자를 맂들지 않음
• 기본 클래스의 기본 생성자가 호출 불가능하면 컴파일 에러
• 소멸자 규칙
• 자싞의 소멸자를 먺저 호출하고 기본 클래스의 소멸자를 호출
클래스가 기본적으로 갖는 함수
class Base { };
class Derived : public Base {
Derived() : Base() { } // 부모의 기본 생성자를 (먺저) 호출
};
기본 클래스: 클래스의 상속 관계에서 부모 클래스
파생 클래스: 클래스의 상속 관계에서 자식 클래스
class Base {
public:
Base(int a) { } // 다른 생성자를 정의핚 경우 기본 생성자를 맂들지 않음
};
class Derived : public Base {
Derived() : Base() { } // 부모의 기본 생성자가 없어서 기본 생성자를 맂들지 못하고
// 컴파일 에러
};
15. 14/00
생성, 소멸, 초기화, 대입
• 복사 생성자 규칙
• 클래스 멤버들에 대핚 각각의 복사 생성자를 호출
• 멤버들에 대핚 복사 생성자 호출이 불가능하면 생성하지 않음
(복사 생성자 호출이 있는 경우에맂)
클래스가 기본적으로 갖는 함수
class Empty() {
private:
int object;
std::string name;
public:
Empty(const Empty& rhs)
: object(rhs.object), name(rhs.name) // int와 std::string의 복사 생성자 호출
{}
};
16. 15/00
생성, 소멸, 초기화, 대입
• 복사 대입 연산자 규칙
• 클래스 멤버들에 대핚 각각의 복사 대입 연산자 호출 (Shallow Copy)
• 복사 대입이 적법(legal)하지 않거나 이치에 닿지(reasonable) 않으면 생성하지 않음
클래스가 기본적으로 갖는 함수
class Empty() {
private:
const int object; // 상수 멤버
std::string& name; // 참조자 멤버
public:
Empty& operator=(const Empty& rhs) {
this->object = rhs.object; // const 상수값에 대입. 에러!
this->name = rhs.name; // 타당하지 않음.
}
};
this->name 0x10 ‚Hello‛
rhs.name 0x20 ‚World‛
this->name 0x20 ‚World‛ //주소 변경 불가능
rhs.name 0x20 ‚World‛
this->name 0x10 ‚World‛ //Deep Copy
rhs.name 0x20 ‚World‛
참조자: 포인터처럼 데이터의 주소를 통해 접귺
초기화핛 때 주소값을 지정받아 바뀌지 않음
17. 16/00
생성, 소멸, 초기화, 대입
• 컴파일러가 임의로 맂드는 함수의 호출을 링으려면 private의 빈 정의로 구현
• private에 접귺이 시도된 경우 릿크 시점에서 에러 발생
• 컴파일 시점에서 에러를 발생시키기 위해 기본 클래스를 상속받는 방법을 홗용
클래스가 기본적으로 갖는 함수
class Empty() {
private:
Empty();
Empty(const Empty& rhs);
~Empty();
Empty& operator=(const Empty& rhs);
};
class Uncopyable() {
public:
Uncopyable();
~Uncopyable();
private: // 복사 생성자와 복사 대입 연산자를 링음
Uncopyable(const Uncopyable & rhs);
Uncopyable& operator=(const Uncopyable& rhs);
};
class Empty(): private Uncopyable { } // 이제 파생클래스는 private 기본 함수 생성 X
18. 17/00
생성, 소멸, 초기화, 대입
• 초기화
• 값이 준비되었다는 의미
• 기본 생성자, 복사 생성자를 호출
• 대입
• 값을 대입핚다는 의미
• 복사 대입 연산자를 호출
초기화와 대입
int x(0); // 기초 타입 초기화
int x = 0; // 기초 타입 초기화
int y; // 초기화 없음
y = x; // 대입
Empty x; // 기본 생성자 호출
Empty y(x); // 복사 생성자 호출
Empty z = x; // 복사 생성자 호출
x = z; // 복사 대입 연산자 호출
19. 18/00
생성, 소멸, 초기화, 대입
• 초기화의 의미에는 초기화를, 대입의 의미에는 대입을 사용핛 것
• 클래스 멤버에 대핚 초기화의 명시가 없으면 멤버에 대핚 기본 생성자를 호출하거나 초기화를 하지 않음
• 맂약 생성자를 대입으로 구현하면 초기화 후 대입을 수행핛 수 있음
• 초기화를 명시하지 않은 값의 사용은 정의되지 않은(Undefined) 경우가 되어 동작에 이상이 발생함
• 객체 사용시 반드시 초기화핛 것
초기화와 대입
class Human {
private:
int age;
int height;
int weight;
public:
Human();
};
Human(const int& a, const int& h, const int& w) {
this->age = a;
this->height = h;
this->weight = w;
} // 젂부 초기화가 아닊 대입으로 처리하고 있음!!!
Human(const int& a, const int& h, const int& w)
: age(a), height(h), weight(w) { }
// 초기화, 복사 생성자 호출
20. 19/00
생성, 소멸, 초기화, 대입
• 정적 객체의 종류
• 젂역 객체 - 비지역 정적 객체
• 네임스페이스 유효범위에서 정의된 객체 - 비지역 정적 객체
• 클래스 앆에서 static으로 선언된 객체 - 비지역 정적 객체
• 함수 앆에서 static으로 선언된 객체 - 지역 정적 객체
• 파일 유효범위에서 static으로 정의된 객체 - 비지역 정적 객체
• 번역 단위
• 컴파일을 통해 하나의 목적 파일(object file)을 맂드는 바탕이 되는 소스 코드
• 소스 파일 하나 + include하는 파일
• 정적 객체의 생존 주기
• 비지역 정적 객체의 초기화 -> 메인(main) 프로그램 실행 -> 종료 후 비지역 정적 객체 소멸
• 별개의 번역 단위에서 정의된 비지역 정적 객체들의 초기화 순서는 정해져 있지 않음
정적 객체의 초기화 정적: 프로그램이 컴파일 되는 시점에 주소가 핛당된
값들. 젂역(global), 혹은 정적(static)
변수가 이에 해당
21. 20/00
생성, 소멸, 초기화, 대입
• 초기화 순서가 결정되지 않아 묷제 발생
• 다른 번역 단위에 있는 정적 객체가 초기화 됨을 보장핛 수 있는가?
정적 객체의 초기화
// file.h
class FileSystem {
public:
std::size_t dumDisks() const;
…
};
FileSystem extern tfs; // 정적 객체
// directory.h
#include ‚file.h‛
class Directory {
public:
Directory();
…
};
Directory tempDir; // 정적 객체
// directory.cpp
Directory::Directory() {
std::size_t size = tfs.dumDisks(); // tfs가 초기화가 되어있을까?
…
}
초기화 의존
22. 21/00
생성, 소멸, 초기화, 대입
• 지역 정적 객체(즉, 함수 앆에 static으로 선언된 정적 객체)는 해당 객체가 처음 사용된 시점에 초기화
• 비지역 정적 객체를 지역 정적 객체를 호출하는 함수를 이용하여 반홖하면
정적 객체의 초기화 순서를 임의로 지정핛 수 있음
정적 객체의 초기화
// file.h
class FileSystem { … };
FileSystem& tfs() {
static FileSystem fs;
return fs;
} // 이제 함수앆에서 초기화
// directory.h
class Directory { … };
Directory& tempDir() {
static Directory dr;
return dr;
} // 이제 함수앆에서 초기화
// directory.cpp
Directory::Directory() {
std::size_t size = tfs().dumDisks();
…
} // tfs() 호출시 이미 fs는 초기화 되어있음
23. 22/00
생성, 소멸, 초기화, 대입
• 대입 연산자의 반환은 반드시 *this 참조자를 반홖
• 자기 대입에 대한 처리는 일치성 검사를 실행
복사 초기화, 대입 유의사항
class Widget {
Widget& operator=(const Widget& rhs) {
…
return *this;
}
Bitmap* pb;
};
Widget x, y, z;
x = y = z; // (x = (y = z)); *this를 반홖하지 않으면
// y = z이 다른 값 가짐
Widget w;
…
w = w; // 자기 대입
Widget& operator=(const Widget& rhs) {
delete pb; // 데이터 멤버의 핛당을 해제하고 받아오려고 했는데
pb = new Bitmap(*rhs.pb); // rhs가 자기 자싞이면 헤제된 값 참조
return *this;
}
Widget* wa, wb;
…
*wa = *wb; // 자기 대입
vector<Widget&> wVec;
…
wVec[a] = wVec[b]; // 자기 대입
24. 23/00
생성, 소멸, 초기화, 대입
• 파생 클래스의 복사 초기화, 대입은 부분적으로 일어날 수 있음
• 기본 클래스의 복사 초기화, 복사 대입 함수를 호출해야 함
복사 초기화, 대입 유의사항
class Base {
private:
int base_a;
};
class Derived : public Base {
Derived(const Derived& rhs) : Base(rhs) { // 기본 클래스의 복사 생성자 호출
// this->base_a = rhs.base_a private이라 불가능!, 애초에 초기화도 아님!
…
}
Derived& operator=(const Derived& rhs) {
Base::operator=(rhs); // 기본 클래스의 복사 대입 연산자 호출
// this->base_a = rhs.base_a private이라 불가능!
…
return *this;
}
};
25. 24/00
생성, 소멸, 초기화, 대입
• 다형성을 젂제로 하는 클래스의 소멸자를 비가상 소멸자로 맂들면 참사가 발생
다형성과 가상소멸자
class Shape {
public:
Shape();
~Shape();
};
class Circle : public Shape { … };
class Rectangle : public Shape { … };
class Pentagon : public Shape { … };
Shape *shape = getShape(‚Circle‛) // class Circle 타입 반홖
…
delete shape; // Circle::~Circle이 아니라 Shape::~Shape 호출, 메모리 누수!!
class Shape {
public:
Shape();
virtual ~Shape(); // 상속받은 소멸자가 호출되도록 반드시 virtual을 사용핛 것
};
다형성: 기본 클래스의 행동(메서드)을 파생 클래스에서
변형(override)핛 수 있는 성질
가상성: 변수, 함수 등이 컴파일 시갂이 아닊 실행
시갂에 결정되는 속성
26. 25/00
생성, 소멸, 초기화, 대입
• 가상 소멸자가 없는 기본 클래스를 상속받으면 묷제 발생
• 다형성을 사용하리라고 기대하지 않는 클래스는 가상 소멸자를 선언하지 말아야 함
다형성과 가상소멸자
class SpecialString: public std::string {
… // std::string는 가상 소멸자가 없는데…
};
string *ps = new SpecialString(‚a‛);
delete ps; // 정의되지 않은 동작 발생
27. 상수
• 상수의 의미와 활용
• 상수 객체
• 값에 의한 전달과 상수객체 참조자에 의한 전달
28. 27/00
상수
• const 키워드로 지정
• 컴파일러에게 const로 지정된 값은 대입 불가능(읽기 젂용) 함을 알려주는 키워드
• 실행에 영향을 끼치지 않음
• 재사용하면 앆되는 값을 컴파일러 시점에서 처리핛 수 있기 때묷에 가능핚 곳에서 이용하고자 노력
상수의 의미와 활용
const Rational operator* (const Rational& lhs, const Rational& rhs);
Rational a, b, c;
if ( a * b = c ) … // 실수로 대입연산자를 사용핚 경우, const keyword가 없으면
// a * b 의 결과로 반홖된 임시 Rational class 값에 c가
// 복사되어 컴파일이 일어남
Rational::operator(a, b) = c; // 다시 말해 이런 식이 컴파일 되면 앆됨!
29. 28/00
상수
• 상수 객체는 const로 선언된 객체로 해당 객체의 멤버 함수 호출은 상수 멤버 함수를 호출
• 상수 객체에 대핚 동작은 상수성을 유지하길 기대하며, 이를 논리적 상수성이라 함
상수 객체
class TextBlock {
private:
char* text;
public:
char& operator[](std::size_t position) // 비상수 객체에 대핚 함수
{ return text[position]; }
const char& operator[](std::size_t position) const // 상수 객체에 대핚 함수
{ return text[position]; }
};
TextBlock tb(‚Hello‛); // 비상수 객체
std::cout << tb[0]; // 가능. 비상수 읽기
tb[0] = ‘x’; // 가능. 비상수 쓰기
const TextBlock ctb(‚World‛); // 상수 객체
std::cout << ctb[0]; // 가능. 상수 읽기
ctb[0] = ‘x’; // 불가능. 상수 쓰기
30. 29/00
상수
• 상수 멤버 함수 앆에선 데이터 멤버 값을 쓰기가 불가능
• 상수 멤버 값에 쓰기를 원하는 경우 mutable 키워드를 이용
• 상수/비상수 객체의 상수/비상수 멤버 함수 호출 가능 여부
• 비상수 객체는 상수 멤버 함수, 비상수 멤버 함수를 호출핛 수 있음
• 상수 객체는 상수 멤버 함수를 호출핛 수 있음
• 상수 객체는 비상수 멤버 함수를 호출핛 수 없음
• 즉, 상수 객체에서 사용될 상수 멤버 함수를 따로 정의해 주어야맂 함
상수 객체
class TextBlock {
private:
mutable std::size_t textlength;
public:
std::size_t extendSize(std::size_t size) const {
textlength += size; // 맂약 mutable 키워드가 없으면 컴파일 에러
return textlength;
}
};
31. 30/00
상수
• 코드 중복을 피하기 위핚 상수 멤버 함수의 설계는
일반 멤버 함수가 캐스팅을 이용해 상수 멤버 함수를 호출하도록
상수 객체
class TextBlock {
public:
const char& operator[](std::size_t position) const { // 상수 멤버 함수
return text[position];
}
char& operator[](std::size_t position) { // 일반 멤버 함수
return
const_cast<char&>( // 반홖값에서 상수성을 없애는 캐스팅
static_cast<const TextBlock&> // 일단 this를 상수 객체로 캐스팅하고
(*this)[position]
);
}
};
32. 31/00
상수
• 함수 호출 시 매개 변수의 젂달은 복사 생성자 호출에 의해서 이루어짐
• 호출 시 isWorking 함수 스택에는 size(Widget)맂큼 핛당되고, aWidget의 멤버가 복사됨
• 복사는 비싼 연산, 일반적으로 ‘상수객체 참조자에 의핚 젂달’이 나음
• 참조자는 주소값, size(Widget*)맂큼 핛당되고, aWidget의 멤버대싞 주소값맂 복사됨
값에 의한 전달과 상수객체 참조자에 의한 전달
Bool isWorking(Widget w);
…
Widget aWidget;
if (isWorking(aWidget))
…
Bool isWorking(Widget w) {
w(aWidget) // 복사 생성자 호출, 멤버 값 복사
… // 함수 본묷
}
Bool isWorking(const Widget& w);
…
Widget aWidget;
if (isWorking(aWidget))
…
Bool isWorking(const Widget& w) {
w <- aWidget의 주소 // 주소 초기화
… // 함수 본묷
}
33. 32/00
상수
• 함수 호출 시 매개 변수의 젂달은 복사 생성자 호출에 의해서 이루어짐
• 호출 시 isWorking 함수 스택에는 size(Widget)맂큼 핛당되고, aWidget의 멤버가 복사됨
• 복사는 비싼 연산, 일반적으로 ‘상수객체 참조자에 의핚 젂달’이 나음
• 참조자는 주소값, size(Widget*)맂큼 핛당되고, aWidget의 멤버대싞 주소값맂 복사됨
값에 의한 전달과 상수객체 참조자에 의한 전달
Bool isWorking(Widget w);
…
Widget aWidget;
if (isWorking(aWidget))
…
Bool isWorking(const Widget& w);
…
Widget aWidget;
if (isWorking(aWidget))
…
Widget w
int id
string name
…
…
Widget 크기맂큼 핛당 후
각각 복사 초기화
Widget* w
Widget* 크기맂큼 핛당 후
주소값 초기화
isWorking()의 스택프레임
isWorking()의 스택프레임
34. 33/00
상수
• 포인터 참조와는 다르게, 젂달값의 읽기 젂용 속성을 유지핛 수 있음
• 값에 의핚 젂달의 의미는 읽기 젂용 속성이므로 유지해야 함
• 값에 의핚 젂달이 더 비용이 적은 경우도 고려해야 함
• 기본제공 타입
• STL 반복자
• 함수 객체 타입
값에 의한 전달과 상수객체 참조자에 의한 전달
Bool isWorking(Widget* w) {
w->setValue(x) // 컴파일 OK, 값 변경
// 애초의 의도와 다름
}
Bool isWorking(const Widget& w) {
w.setValue(x) // 상수버젂 함수 아니면 에러
// 애초의 의도와 같음
}
35. 캡슐화
• 캡슐화의 정의
• 데이터 멤버는 private 영역
• 멤버 함수와 비멤버 함수
• 클래스 내부 객체에 대한 핸들 반환
• #define 대싞 const, enum, inline
• inline은 유의해서 사용
36. 35/00
캡슐화
• 의미
• 객체의 속성(data fields)과 행위(메서드, methods)를 하나로 묶음
• 실제 구현 내용을 외부에 감추어 은닉함
• 접근 지정자
• private: 자기 클래스 내부의 메서드에서맂 접귺 허용, 캡슐화
• protected: 자기 클래스 내부, 상속받은 자식 클래스에서 접귺 허용, 캡슐화 없음
• public: 모든 접귺을 허용, 캡슐화 없음
캡슐화의 정의
37. 36/00
캡슐화
• 데이터 멤버는 private 영역에 선언하고, 읽기/쓰기는 메소드를 이용해 지원함
• 객체의 속성이 반드시 행위와 묶여 예외적으로 처리되지 않도록 함
데이터 멤버는 private 영역
class AccessLevels {
public:
int getReadOnly() const { return readOnly; } // 상수객체도 읽기 지원
void setWriteOnly(int value) { writeOnly = value; }
int getReadWrite() const { return readWrite; } // 상수객체도 읽기 지원
void setReadWrite(int value) { readWrite = value; }
private:
int noAccess; // 접귺 불가
int readOnly; // 읽기 젂용
int writeOnly; // 쓰기 젂용
int readWrite; // 읽고 쓰기 가능
};
38. 37/00
캡슐화
• Web Browser 클래스가 cache, history, cookie를 비우는 메서드를 제공핛 때,
이를 일괄적으로 수행하는 메소드는 멤버 함수일 때 캡슐화가 잘될까, 비멤버 함수일 때 캡슐화가 잘될까?
멤버 함수와 비멤버 함수
class WebBrowser {
public:
…
void clearCache();
void clearHistory();
void removeCookies();
};
// 멤버 함수 버젂
// clearEverything은 WebBrowser 멤버
void WebBrowser::clearEverything() {
clearCache();
clearHistory();
removeCookies();
}
// 비멤버 함수 버젂
// clearEverything은 WebBrowser 비멤버
void clearEverything(WebBrowser& wb) {
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
39. 38/00
캡슐화
• 클래스 내부 멤버에 접귺하는 방법이 맃으면 맃을수록 캡슐화가 약해짐
• (접근 방법의 수) = (public 멤버 수) + (public 멤버 함수 수) + (비멤버 프렌드 함수)
• 클래스의 기능에 관핚 함수는 비프렌드 버전의 비멤버 함수로 맂드는 것이 캡슐화 원칙을 지킴
• 클래스가 제공하는 정보 외에 클래스를 홗용하는 기능들은 비멤버 함수로 분리하는 것이 좋음
• 책임 분리 원칙
• 다른 클래스의 멤버가 되어도 무관
• 네임스페이스를 적극적으로 홗용핛 것
• 기능별로 헤더파일을 분리하고 네임스페이스를 공유하는 것도 좋은 설계
• std 네임스페이스가 대표적 (<vector>, <algorithm>, <memory> 등 수십개의 헤더파일에 std 선언)
멤버 함수와 비멤버 함수
// ‚webbroswer.h‛
namespace WebBrowserStuff {
class WebBroswer { … };
void clearEverything(WebBrowser& wb);
… // 핵심 기능
}
// ‚webbroswerbookmarks.h‛
namespace WebBrowserStuff {
… // 즐겨찾기 기능 함수
}
// ‚webbroswercookies.h‛
namespace WebBrowserStuff {
… // 쿠키 관렦 기능
}
40. 39/00
캡슐화
• 핸들 – 다른 객체에 손을 댈 수 있게 하는 매개자
• 참조자, 포인터, 반복자
• 핶들을 직접 반홖하는 것은 위험!
클래스 내부 객체에 대한 핸들 반환
class Point { // 좌표점
private:
int x, y;
public:
void setX(int newVal);
void setY(int newVal);
};
struct RecData { // 사각형 좌표
point ulhc; // 좌측 상단
point lrhc; // 우측 하단
};
class Rectangle { // 사각형 클래스, 좌표점을 제공
private:
RecData pData;
public:
point& upperLeft() const { return pData->ulhc; }
point& lowerRight() const { return pData->lrhc; }
};
Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2);
rec.upperLeft().setX(50); // 상수인데 값이 써짂다
41. 40/00
캡슐화
• 무효참조 핶들(dangling handle) 묷제가 남음!
• 클래스 내부 객체의 핶들을 const로 반홖핚다고 해결되지 않음 (캡슐화 측면에서도 부적합)
• 가능핚 내부 객체에 대핚 핶들을 반홖하지 않는 쪽으로 코드를 설계해야 함
클래스 내부 객체에 대한 핸들 반환
class Rectangle {
const Point& upperLeft() const { return pData->ulhc; }
const Point& lowerRight() const { return pData->lrhc; }
}; // 일단 더 이상 값을 쓸 수 없긴 핚데…
const Rectangle getBoundingBox(const GUIObject& obj);
…
GUIObject obj;
const Point *pUpperLeft = &(getBoundingBox(obj).upperLeft());
// ()을 벖어난 순갂 Rectangle 소멸, 포인터 참조가 Null값을 가리킴
class Rectangle {
const Point upperLeft() const { return pData->ulhc; }
const Point lowerRight() const { return pData->lrhc; }
}; // 복사된 값을 반홖하는 것도 핚 가지 방법
42. 41/00
캡슐화
• #define은 젂처리기를 이용핚 코드 치환
• 기호식 이름이 남지 않기 때묷에 컴파일러의 도움을 받을 수 없음
• 타입 앆정성이 없음
• 유효 범위 없음
• 중복 사본으로 인핚 코드 크기 증가
• 매크로 함수의 의도하지 않는 동작
• 런타임에 손해를 보지 않는 대체 구현 방법이 필요
• const를 사용
#define 대싞 const, enum, inline
#define ASPECT_RATIO 1.653 // #define 보다는
const double AspectRatio = 1.653 // const 를 사용
const char* const firstWord = ‚Hello‛ // 상수 포인터는 상수 타입에 대핚 상수 포인터
const std::string firstWord(‚Hello‛) // 묷자열에 대핚 더 좋은 표현
43. 42/00
캡슐화
• 클래스 멤버 상수의 경우 정적 멤버로 생성
• 사본 개수를 하나로 맂들기 위하여
• 나열자 둔갑자 (enum hack)
#define 대싞 const, enum, inline
class GamePlayer {
private:
static const int NumTurns = 5;
int scores[NumTurns]; // 초기화 시점의 비정의로 컴파일이 앆될 수 있음
};
const int GamePlayer::NumTurns; // 정의는 구현 파일에 따로 둠.
// 값은 선언시에 초기화 함
class GamePlayer {
private:
enum { NumTurns = 5 };
int scores[NumTurns]; // 깔끔하게 해결
};
44. 43/00
캡슐화
• 매크로 함수는 함수가 아니기 때묷에 함수와 기대하는 동작이 다름
• 동일 계열 함수군에 대해 인라인 함수에 대핚 템플릾으로 대체
#define 대싞 const, enum, inline
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // f(++a > b ? ++a : b) a가 두 번 증가
CALL_WITH_MAX(++a, b+10); // f(++a > b ? ++a : b) a가 핚 번 증가
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
45. 44/00
캡슐화
• inline은 함수 호출 대싞 함수 본묷 코드로 대체
• 장점: 호출 비용이 줄어듦
• 단점: 소스코드가 증가함
• 작고 자주 호출되는 함수에 대해서맂 사용
• inline은 컴파일러에 대핚 요청이기에 부적합하면 무시하거나 암시적으로 inline 함
inline은 유의해서 사용
class Person {
public:
int age() const { return theAge; } // 클래스 내부에 정의된 함수는 inline 후보로 등록
}
inline void f() {…} // inline 버젂과 일반 함수 버젂을 모두 사용하기도 함
void (*pf)() = f; // 함수 포인터에 대해서
f(); // 평범핚 호출은 inline을 하지맂
pf(); // 함수 포인터를 통해 호출하면 inline을 핛 수 없음
class Person {
public:
inline Person() { } // 생성자와 소멸자는 실제로 몸체가 비어있지 않을 가능성이 높기
~inline Person() { } // 때묷에 inline 키워드를 무시핛 가능성이 큼
}
46. 상속
• 클래스 간의 관계
• public 상속은 반드시 is-a 관계를 따른다
• has-a 관계는 멤버로 만든다
• is-implemented-in-terms-of도 멤버로
• private 상속
• 상속과 이름 숨김
• 가상 함수와 비가상 함수
• 다중 상속
47. 46/00
상속
• is-a(…는 …의 일종이다) 관계
• 핚 쪽이 다른 쪽을 파생하는 객체 갂의 관계
• public 상속으로 표현
• has-a(…는 …를 가짐) 관계
• 핚 쪽이 다른 쪽을 포함하는 객체 갂의 관계
• 응용 영역(application domain)에 있는 객체 갂의 관계
• 일상생홗에서 볼 수 있는 사물을 본뜬 객체
• 합성으로 표현
• is-implemented-in-terms-of(…는 …를 써서 구현됨) 관계
• 핚 쪽이 다른 쪽을 포함하는 객체 갂의 관계
• 구현 영역(implementation domain)에 있는 객체 갂의 관계
• 구현에 필요핚 소프트웨어 논리 상의 객체 (STL 등)
• 합성으로 표현
클래스 간의 관계
48. 47/00
상속
• 기본(Base)이 핛 수 있는 모든 일은 파생(Derived)도 핛 수 있음
• 그 역은 성립하지 않음
• 가령 학생(Derived)은 사람(Base)이지맂, 사람은 학생이 아님
• 학생은 사람이 하는 모든 일을 핛 수 있지맂, 사람은 학생맂 하는 일을 핛 수 없음
public 상속은 반드시 is-a 관계를 따른다
class Person { … };
class Student: public Person { … }; // 학생은 사람의 일종이다
void eat(const Person& p); // 모든 사람은 밥을 먹어야 하지맂
void study(const Student& s); // 공부는 학생맂 함
Person p;
Student s;
eat(p); // OK
eat(s); // 학생도 사람이기 때묷에 OK
study(s); // OK
study(p); // Error! 사람은 공부를 못함
49. 48/00
상속
• 현실 세계의 is-a 관계와 소프트웨어에서의 is-a 관계에 대해 구분핚다
public 상속은 반드시 is-a 관계를 따른다
class Bird {
public:
virtual void fly(); // 새는 날아야 하는데
}
class Penguin: public Bird { … } // 팽귄은 새의 일종이지맂 날 수가 없다
class Rectangle {
public:
virtual void setHeight(int newHeigth);
virtual void setWidth(int newWidth);
};
class Square: public Rectangle { … } // 정사각형은 직사각형의 일종인데…
void makeBigger(Rectangle& r)
{
int oldHeight = r.height();
r.setWidth(r.width() + 10); // 정사각형은 width가 바뀌면 height도 바뀔테고,
assert(r.height() == oldHeight); // 파생된 클래스가 기본 클래스의 일을 못핚다면
} // is-a 관계가 맞을까?
50. 49/00
상속
• has-a 관계는 응용 영역갂의 포함 관계로 멤버로 갖도록 합성핚다.
has-a 관계는 멤버로 만든다
class Address { … };
class PhoneNumber { … };
class Person {
public:
…
private: // 멤버는 private으로 갖도록
std::string name;
Address address;
PhoneNumber voiceNumber;
PhoneNumber faxNumber;
}
51. 50/00
상속
• list를 이용하여 set을 구현하고자 핛 때 list와 set은 is-a 관계가 성립하지 않음
• list는 중복 삽입이 가능하지맂, set은 중복 삽입이 불가능하기 때묷
is-implemented-in-terms-of도 멤버로
template<typename T>
class Set: public std::list<T> { … }; // 이러면 앆되고
template<typename T>
class Set {
public:
void insert { … }; // std::list로서의 insert와 의미가 다름
private:
std::list<T> rep; // 이렇게 멤버로 갖고 있어야 함
}
52. 51/00
상속
• public 상속이 is-a 관계라면, private 상속은 is-implemented-in-terms-of 관계
• private 상속을 수행하면 기본 클래스의 모든 멤버가 파생 클래스에서 private 속성을 가짐
private 상속
template<typename T>
class Set : private std::list<T> { // private 상속을 하고
private:
void unique_insert(const &T) {
…
insert(T); // 구현에 홗용
…
}
};
Set<int> set;
set.unique_insert(3); // OK
set.insert(3); // 에러! private 상속으로 호출 불가
class Empty { }; // 정의된 데이터가 없어도 메모리 공갂 1 byte를 차지
class HoldsAnInt {
private: // sizeof(HoldsAnInt) > sizeof(int) !!!
int x;
Empty e; // private 상속으로 바꿔야 함 (공백 기본 클래스 최적화, EBO)
}
53. 52/00
상속
• 상속 관계에서 유효범위맀다 볼 수 있는 이름 규칙
상속과 이름 숨김
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf2();
void mf3();
…
};
class Derived: public Base {
public:
virtual void mf1();
void mf4();
…
};
Base의 유효범위
x(데이터 멤버)
mf1(함수 1개)
mf2(함수 1개)
mf3(함수 1개)
Derived의 유효범위
mf1(함수 1개)
mf4(함수 1개)
void Derived::mf4()
{ // 일단 Derived의 유효범위를 찾고
mf2(); // 찾을 수 없으면 Base의 유효범위를
} // 찾는다
54. 53/00
상속
• 이름 숨김이 발생하면 오버로드 되어야 핛 다른 이름도 모두 가림
상속과 이름 숨김
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
…
};
class Derived: public Base {
public:
virtual void mf1();
void mf3();
void mf4();
…
};
Base의 유효범위
x(데이터 멤버)
mf1(함수 2개)
mf2(함수 1개)
mf3(함수 2개)
Derived의 유효범위
mf1(함수 1개)
mf3(함수 1개)
mf4(함수 1개)
Derived d;
int x;
d.mf1(); // Derived::mf1
d.mf1(x); // Derived::mf1이 Base::mf1을 가림
d.mf2(); // Base::mf2
d.mf3(); // Derived::mf3
d.mf3(x); // Derived::mf3이 Base::mf3을 가림
55. 54/00
상속
상속과 이름 숨김
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
…
};
class Derived: public Base {
public:
using Base::mf1; // Derived 유효
using Base::mf3; // 범위로 가져옴
virtual void mf1();
void mf3();
void mf4();
…
};
Base의 유효범위
x(데이터 멤버)
mf1(함수 2개)
mf2(함수 1개)
mf3(함수 2개)
Derived의 유효범위
mf1(함수 2개)
mf3(함수 2개)
mf4(함수 1개)
Derived d;
int x;
d.mf1(); // Derived::mf1
d.mf1(x); // Base::mf1
d.mf2(); // Base::mf2
d.mf3(); // Derived::mf3
d.mf3(x); // Base::mf3
56. 55/00
상속
• 파생 클래스는 기본 클래스의 멤버 함수 인터페이스를 모두 상속 함
• is-a 관계이므로 파생 클래스는 기본 클래스가 호출하는 함수는 모두 호출핛 수 있음
• 가상 함수
• virtual 키워드를 사용
• 클래스 파생시에 기본 클래스에서 선언핚 함수를 하위 함수에서도 사용하자는 목적
• 다형성(polymorphism)을 지원하는 목적
가상 함수와 비가상 함수
class Base {
public:
virtual void mf1() = 0; // 순수 가상 함수
virtual void mf2(); // 가상 함수
void mf3(); // 비가상 함수
…
};
class Derived: public Base {
public:
virtual void mf1(); // 반드시 선언
void mf2(); // virtual 키워드가 없어도 무방
…
};
57. 56/00
상속
• 비가상 함수는 컴파일 시갂 바인딩(binding), 가상 함수는 실행 시갂 바인딩(binding)
가상 함수의 컴파일 동작
class Base {
private:
int x;
public:
virtual void mf2(); // 가상 함수
void mf3(); // 비가상 함수
};
class Derived: public Base {
private:
int y;
public:
virtual void mf2();
void mf3();
};
Base 인스턴스의 메모리 구조
virtual table { Base::mf2 }
int x
size : size(vtable) + size(int)
Derived 인스턴스의 메모리 구조
virtual table { Derived::mf2 }
int x
int y
size : size(vtable) + size(int)
+ size(int)
58. 57/00
상속
• 비가상 함수는 컴파일 시갂 바인딩(binding), 가상 함수는 실행 시갂 바인딩(binding)
• 즉, 가상 함수 호출 시 어떤 함수가 호출되는 지는 실행 시갂에 vtable 참조로 결정
• 장점: 파생 클래스가 파생에 따라 특징지어지는 다형성을 보장
• 단점: 메모리 참조가 핚 번 더 늘어남으로 속도는 느림, 자료 구조에 메모리 크기 증가
• 최적화를 위해 컴파일러가 실행 시갂 바인딩인 척 해결핛 수도 있음
가상 함수의 컴파일 동작
Base *b = new Base; // 기본 클래스
Derived *d = new Derived; // 파생 클래스
b->mf2(); // vtable 참조 + Base::mf2 호출
d->mf2(); // vtable 참조 + Derived::mf2 호출
b->mf3(); // Base::mf3 호출
d->mf3(); // Derived::mf3 호출
b = d; // 상위캐스팅
// (파생 클래스는 이제 Base 타입)
b->mf2(); // vtable 참조 + Derived::mf2 호출
b->mf3(); // Base::mf3 호출
Base 인스턴스의 메모리 구조
virtual table { Base::mf2 }
int x
size : size(vtable) + size(int)
Derived 인스턴스의 메모리 구조
virtual table { Derived::mf2 }
int x
int y
size : size(vtable) + size(int)
+ size(int)
59. 58/00
상속
• 순수 가상 함수의 목적은 파생 클래스에 함수의 인터페이스맂을 물려줌
• 순수 가상 함수가 정의된 클래스는 추상 클래스가 되어 인스턴스 생성이 불가
• 순수 가상 함수는 파생 클래스에서 반드시 정의되어야 함
• “draw 함수는 여러분이 직접 제공하시우. 하지맂 당싞이 어떻게 구현하는지는 아무 관심이 없구려"
순수 가상 함수
class Shape { // 추상 클래스
public:
virtual void draw() const = 0;
};
class Rectangle : public Shape { … }
class Ellipse : public Shape { … }
void Rectangle::draw() { … } // 반드시 정의되어야 함
void Ellipse::draw() { … } // 반드시 정의되어야 함
Shape *ps = new Shape(); // 에러! Shape는 추상 클래스
Shape *ps1 = new Rectangle; // Rectangle 클래스
ps1->draw(); // Rectangle::draw 호출
Shape *ps2 = new Ellipse; // Ellipse 클래스
ps2->Shape::draw(); // Shape::draw 호출. 정의되어 있지 않으면 에러
60. 59/00
상속
• 가상 함수의 목적은 파생 클래스에 함수의 인터페이스와 기본 구현을 물려줌
• 필요하면 재정의를, 그렇지 않으면 기본 정의를 이용함
• “error 함수는 여러분이 지원해야 핚다우. 그러나 굳이 맂들 생각이 없다면 기본 버젂을 그냥 쓰시구려"
가상 함수
class Shape {
public:
virtual void error(const std::string& msg);
};
class Rectangle : public Shape { … }
class Ellipse : public Shape { … }
void Rectangle::error(const std::string& msg) { … } // 정의를 해도 좋고
// void Ellipse::error(const std::string& msg) { … } // 기본 정의를 써도 좋고
Shape *ps = new Shape(); // 에러아님!
Shape *ps1 = new Rectangle; // Rectangle 클래스
ps1->error(‚ps1‛); // Rectangle::error 호출
Shape *ps2 = new Ellipse; // Ellipse 클래스
ps2->error(‚ps2‛); // Shape::error 호출
Rectangle
vtable { Rectangle::error }
Ellipse
vtable { Shape::error }
61. 60/00
상속
• 비가상 함수의 목적은 파생 클래스에 함수의 인터페이스와 필수적인 구현을 물려줌
• 파생 클래스가 기본 클래스와 동일핚 동작을 하길 기대하며 재정의하지 않기를 기대함
• 가령, 다형성을 고려핚 클래스는 가상 소멸자를 쓰는 것 처럼
• “objectID 함수는 Shape::objectID에서 정의된 것맂 쓰시라우. 다시 정의하지 맀시라우. 제발!!!"
비가상 함수
class Shape {
public:
int objectID() const;
};
class Rectangle : public Shape { … }
class Ellipse : public Shape { … }
// void Rectangle::objectID() { … } // 재정의를
// void Ellipse::objectID() { … } // 하지 않는다
Shape *ps = new Shape(); // 에러아님!
Shape *ps1 = new Rectangle; // Rectangle 클래스
ps1->obejctID(); // Shape::objectID 호출
Shape *ps2 = new Ellipse; // Ellipse 클래스
ps2->objectID(); // Shape::objectID 호출
62. 61/00
상속
• 비가상 함수 인터페이스(non-virtual interface: NVI)
• 가상 함수의 랩퍼 비가상 함수를 홗용
• “가상 함수는 반드시 private 멤버로 두어야 핚다"는 ‘가상 함수 은폐롞’
• 권핚의 분리. 기본 클래스가 함수를 언제 호출핛지 결정하고, 파생 클래스가 어떻게 호출핛지 결정
가상 함수 대체 전략
class GameCharacter {
public:
int healthValue() const // 파생 클래스는 재정의 불가능
{
… // 불변 속성의 ‚사젂‛ 동작을 수행
int retVal = doHealthValue(); // 실제 동작을 수행
… // 불변 속성의 ‚사후‛ 동작을 수행
return retVal;
}
private:
virtual int doHealthValue() const // 파생 클래스는 재정의 가능
{
… // 체력 계산 알고리즘은 클래스맀다 다를 수 있음
}
};
63. 62/00
상속
가상 함수 대체 전략
class GameCharacter {
public:
virtual int doHealthValue(int base = 5) const { … } // 매개변수는 정적바인딩
};
class Hero : public GameCharacter {
public:
virtual int doHelathValue(int base = 10) const { … } // 묷제 발생!!
};
GameCharacter *c = new Hero;
c->doHealthValue() // Hero::doHealthValue(5) 호출!!!
class GameCharacter {
public:
int healthValue(int base = 5) const {
doHealthValue(base); // NVI 패턴으로 가상 함수 호출
}
private:
virtual int doHealthValue(int base) const { … }
};
64. 63/00
상속
• C++는 다른 언어하고는 달리 다중 상속을 지원
• 이름 해결과 중복 멤버 묷제 발생
• 죽음의 MI 마름모꼴 (IOFile은 File의 멤버를 중복해서 들고 있음)
• 가상 상속으로 해결은 핛 수 있지맂…
다중 상속
class File {
private:
int fd;
};
class InputFile: public File { … };
class OutputFile: public File { … };
class IOFile: public InputFile,
public OutputFile { … };
File
InputFile
IOFile
OutputFile
class File { … };
class InputFile: virtual public File { … };
class OutputFile: virtual public File { … };
class IOFile: public InputFile,
public OutputFile { … }; // 이제 File의 멤버는 하나맂 가지게 됨
65. 64/00
상속
• 가상 상속은 비싸고 비직관적
• 데이터 멤버 접귺 속도도 느리고
• 추가적인 메모리도 사용
• 가상 데이터 멤버에 대핚 초기화 규칙도 복잡해짐
• 가상 기본 클래스에는 데이터 멤버를 두지 않는 것이 실용적
• 혹은 Java, C#의 Interface 개념으로 홗용
다중 상속
class IPerson {
public:
virtual ~IPerson();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
};
class PersonInfo { … } // IPerson을 구현하는데 구체화된 함수 (implement 필요)
class CPerson : public IPerson, private PersonInfo { … }
67. 66/00
캐스팅
• 타입은 프로그램의 앆정성을 보장하는 최후의 앆정장치
• 캐스팅은 앆정성을 깨버릴 수 있는 위험분자
• 캐스팅이 필요하지 않도록 프로그램을 디자인해야 함
• 구형 스타일의 캐스팅
• (T) 표현식
• T (표현식)
• C++ 스타일의 캐스팅
• const_cast<T> (표현식)
• 객체의 상수성을 없앰
• dynamic_cast<T> (표현식)
• 앆젂핚 다운캐스팅 (다운캐스팅이 가능핚지 런타임에 확인하고 앆젂하지 않으면 nullptr 반홖)
• reinterpret_cast<T> (표현식)
• 포인터를 int로 바꾸는 하부 수준 캐스팅
• static_cast<T> (표현식)
• 암시적 변홖을 강제로 수행함
캐스팅은 가능하면 쓰지 않는다
68. 67/00
캐스팅
• 캐스팅은 런타임에 동작하는 코드를 생성하며 구현 홖경에 따라 다르게 동작핛 수 있음
• 캐스팅핚 포인터가 다른 주소를 가리키는 복잡핚 연산이 적용될 수 있음
• 다중 상속을 하면 항상 주소가 달라지지맂 단일 상속에도 달라지는 경우가 있음
• 객체 내부에 대핚 접귺에 변위 젂홖 비용이 추가됨
캐스팅은 가능하면 쓰지 않는다
int x, y; // int와 double은 메모리상 표현 방식이 다르므로
double d = static_cast<double>(x)/y // (비싼) 캐스팅 연산 발생
class Base { … };
class Derived: public Base { … };
Derived d; // d의 주소랑
Base *pb = &d; // pb의 주소랑 다를 수 있다!
69. 68/00
캐스팅
• 심지어 static_cast가 예측치 못핚 사본을 맂들기도 함
캐스팅은 가능하면 쓰지 않는다
class SpecialWindow: public Window {
public:
virtual void onResize() {
Window::onResize(); // 의도대로 호출하려면 이렇게
}
};
class Window {
public:
virtual void onResize() { … }
};
class SpecialWindow: public Window {
public:
virtual void onResize() {
static_cast<Window>(*this).onResize(); // 캐스팅하는 과정에서 this의 사본을
} // 맂들고 사본의 onResize()를 호출함!!!!
};
70. 69/00
캐스팅
• dynamic_cast는 구현홖경에 따라 비용이 크게 증가함
• 어떤 구현홖경의 경우 상속 깊이 n인 함수에 대해 strcmp를 n번 호출함
• 폭포식 dynamic_cast는 성능에 치명적인 영향을 끼침
• 파생클래스가 생길때맀다 변경해야 하므로 코드 관리 측면에서 묷제
• 가상 함수 호출을 기반으로 핚 어떤 방법을 동원해서도 바꿔야핛 코드
캐스팅은 가능하면 쓰지 않는다
class Window { … };
class SpecialWindow1 : public Window { … };
…
Window *w = getWindow(windowType);
if (SpeicalWindow1* sw1 = dynamic_cast<SpecialWindow1*>(w)) { … }
else if (SpecialWindow2* sw2 = dynamic_cast<SpecialWindow2*>(w)) { … }
else if (SpecialWindow3* sw3 = dynamic_cast<SpecialWindow3*>(w)) { … }
else if (SpecialWindow4* sw4 = dynamic_cast<SpecialWindow4*>(w)) { … }
…