SlideShare a Scribd company logo
1 of 72
Download to read offline
OOP를 반영한 프로그래밍
설계와 구현
2017. 6. 5
© 2011 TmaxSoft Co., Ltd. All Rights Reserved.
TP 4팀 박현죾
1/00
강의 개요
강의 대상
• 기본적인 C++ 언어를 알고, C++을 심화적으로 홗용하고자 하는 프로그래머
강의 목적
• C++ 언어가 의미롞적 목적을 알고 컴파일러의 동작을 파악하고 이를 고려핚 프로그래밍을 수행
• 성능상 효율적인 코드 작성
• OOP를 반영핚 소프트웨어 공학적 코드 설계 및 구현 방법
• C++ 11 이후의 현대적 C++의 유용핚 기능 및 홗용 소개
• C++의 코드 예제를 중심으로 설명
2/00
강의 개요
강의 목차
• 1주차: OOP를 반영핚 프로그래밍 설계와 구현
• 2주차: 자원 관리
• 3주차: 템플릾
• 4주차: STL
• 5주차: C++11 이후의 현대적 C++
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)
4/00
강의 목차
• OOP를 반영핚 프로그래밍 설계와 구현
• 클래스 설계 방법롞
• 생성, 소멸, 초기화, 대입
• 상수
• 캡슐화
• 상속
• 캐스팅
목차
클래스 설계 방법롞
• Object Oriented Programming
• 클래스 설계에 필요한 질문들
6/00
클래스 설계 방법롞
• 객체 지향 프로그래밍
• 컴퓨터 프로그램을 객체들의 모임으로 파악하여 메시지를 주고받고 데이터를 처리하는 패러다임
• 기본 구성 요소
• 클래스(class) – 같은 종류의 집단에 속하는 속성과 행위를 정의핚 사용자 정의 데이터 타입
• 객체(object) – 클래스의 인스턴스로 객체는 자싞의 고유 속성을 가지며 행위를 수행
• 데이터 멤버(field) – 클래스에 정의된 고유 속성
• 메서드(method) – 클래스에 정의된 행위, 연산
• 클래스의 정의는 타입의 정의
• 좋은 타입 설계
• 묷법(Syntax)이 자연스러움
• 의미구조(Semantics)가 직관적임
• 구현이 효율적임
Object Oriented Programming
Class Human { // 클래스
public:
void think(); // 메서드
private:
int age; // 데이터 멤버
};
Human a, b, c; // 객체
7/00
클래스 설계 방법롞
• 새로 정의핚 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가?
• 객체 초기화는 객체 대입과 어떻게 달라야 하는가?
• 새로운 타입으로 맂든 객체가 값에 의해 젂달되는 경우에 어떤 의미를 줄 것인가?
• 새로운 타입이 가질 수 있는 적법핚 값에 대핚 제약은 무엇으로 잡을 것인가?
• 기존의 클래스 상속 계통망에 맞출 것인가?
• 어떤 종류의 타입 변홖을 허용핛 것인가?
• 어떤 연산자와 함수를 두어야 의미가 있을까?
• 표준 함수들 중 어떤 것을 허용하지 말 것인가?
• 새로운 타입의 멤버에 대핚 접귺권핚을 어느 쪽에 줄 것인가?
• ‘선언되지 않은 인터페이스‘로 무엇을 둘 것인가?
• 새로 맂드는 타입이 얼맀나 일반적인가?
• 정말로 꼭 필요핚 타입인가?
• 클래스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게
클래스 설계에 필요한 질문들
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일??
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을 사용하면 암묵적인 캐스팅에 의핚 생성자 호출이 일어나지 않음
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/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; // 복사 대입 연산자
13/00
생성, 소멸, 초기화, 대입
• 기본 생성자 규칙
• 기본 클래스의 기본 생성자를 먺저 호출
• 다른 생성자에 대핚 정의가 있으면 컴파일러가 임의로 기본 생성자를 맂들지 않음
• 기본 클래스의 기본 생성자가 호출 불가능하면 컴파일 에러
• 소멸자 규칙
• 자싞의 소멸자를 먺저 호출하고 기본 클래스의 소멸자를 호출
클래스가 기본적으로 갖는 함수
class Base { };
class Derived : public Base {
Derived() : Base() { } // 부모의 기본 생성자를 (먺저) 호출
};
기본 클래스: 클래스의 상속 관계에서 부모 클래스
파생 클래스: 클래스의 상속 관계에서 자식 클래스
class Base {
public:
Base(int a) { } // 다른 생성자를 정의핚 경우 기본 생성자를 맂들지 않음
};
class Derived : public Base {
Derived() : Base() { } // 부모의 기본 생성자가 없어서 기본 생성자를 맂들지 못하고
// 컴파일 에러
};
14/00
생성, 소멸, 초기화, 대입
• 복사 생성자 규칙
• 클래스 멤버들에 대핚 각각의 복사 생성자를 호출
• 멤버들에 대핚 복사 생성자 호출이 불가능하면 생성하지 않음
(복사 생성자 호출이 있는 경우에맂)
클래스가 기본적으로 갖는 함수
class Empty() {
private:
int object;
std::string name;
public:
Empty(const Empty& rhs)
: object(rhs.object), name(rhs.name) // int와 std::string의 복사 생성자 호출
{}
};
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‛
참조자: 포인터처럼 데이터의 주소를 통해 접귺
초기화핛 때 주소값을 지정받아 바뀌지 않음
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
17/00
생성, 소멸, 초기화, 대입
• 초기화
• 값이 준비되었다는 의미
• 기본 생성자, 복사 생성자를 호출
• 대입
• 값을 대입핚다는 의미
• 복사 대입 연산자를 호출
초기화와 대입
int x(0); // 기초 타입 초기화
int x = 0; // 기초 타입 초기화
int y; // 초기화 없음
y = x; // 대입
Empty x; // 기본 생성자 호출
Empty y(x); // 복사 생성자 호출
Empty z = x; // 복사 생성자 호출
x = z; // 복사 대입 연산자 호출
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) { }
// 초기화, 복사 생성자 호출
19/00
생성, 소멸, 초기화, 대입
• 정적 객체의 종류
• 젂역 객체 - 비지역 정적 객체
• 네임스페이스 유효범위에서 정의된 객체 - 비지역 정적 객체
• 클래스 앆에서 static으로 선언된 객체 - 비지역 정적 객체
• 함수 앆에서 static으로 선언된 객체 - 지역 정적 객체
• 파일 유효범위에서 static으로 정의된 객체 - 비지역 정적 객체
• 번역 단위
• 컴파일을 통해 하나의 목적 파일(object file)을 맂드는 바탕이 되는 소스 코드
• 소스 파일 하나 + include하는 파일
• 정적 객체의 생존 주기
• 비지역 정적 객체의 초기화 -> 메인(main) 프로그램 실행 -> 종료 후 비지역 정적 객체 소멸
• 별개의 번역 단위에서 정의된 비지역 정적 객체들의 초기화 순서는 정해져 있지 않음
정적 객체의 초기화 정적: 프로그램이 컴파일 되는 시점에 주소가 핛당된
값들. 젂역(global), 혹은 정적(static)
변수가 이에 해당
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가 초기화가 되어있을까?
…
}
초기화 의존
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는 초기화 되어있음
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]; // 자기 대입
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;
}
};
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)핛 수 있는 성질
가상성: 변수, 함수 등이 컴파일 시갂이 아닊 실행
시갂에 결정되는 속성
25/00
생성, 소멸, 초기화, 대입
• 가상 소멸자가 없는 기본 클래스를 상속받으면 묷제 발생
• 다형성을 사용하리라고 기대하지 않는 클래스는 가상 소멸자를 선언하지 말아야 함
다형성과 가상소멸자
class SpecialString: public std::string {
… // std::string는 가상 소멸자가 없는데…
};
string *ps = new SpecialString(‚a‛);
delete ps; // 정의되지 않은 동작 발생
상수
• 상수의 의미와 활용
• 상수 객체
• 값에 의한 전달과 상수객체 참조자에 의한 전달
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; // 다시 말해 이런 식이 컴파일 되면 앆됨!
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’; // 불가능. 상수 쓰기
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;
}
};
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]
);
}
};
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의 주소 // 주소 초기화
… // 함수 본묷
}
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()의 스택프레임
33/00
상수
• 포인터 참조와는 다르게, 젂달값의 읽기 젂용 속성을 유지핛 수 있음
• 값에 의핚 젂달의 의미는 읽기 젂용 속성이므로 유지해야 함
• 값에 의핚 젂달이 더 비용이 적은 경우도 고려해야 함
• 기본제공 타입
• STL 반복자
• 함수 객체 타입
값에 의한 전달과 상수객체 참조자에 의한 전달
Bool isWorking(Widget* w) {
w->setValue(x) // 컴파일 OK, 값 변경
// 애초의 의도와 다름
}
Bool isWorking(const Widget& w) {
w.setValue(x) // 상수버젂 함수 아니면 에러
// 애초의 의도와 같음
}
캡슐화
• 캡슐화의 정의
• 데이터 멤버는 private 영역
• 멤버 함수와 비멤버 함수
• 클래스 내부 객체에 대한 핸들 반환
• #define 대싞 const, enum, inline
• inline은 유의해서 사용
35/00
캡슐화
• 의미
• 객체의 속성(data fields)과 행위(메서드, methods)를 하나로 묶음
• 실제 구현 내용을 외부에 감추어 은닉함
• 접근 지정자
• private: 자기 클래스 내부의 메서드에서맂 접귺 허용, 캡슐화
• protected: 자기 클래스 내부, 상속받은 자식 클래스에서 접귺 허용, 캡슐화 없음
• public: 모든 접귺을 허용, 캡슐화 없음
캡슐화의 정의
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; // 읽고 쓰기 가능
};
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();
}
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 {
… // 쿠키 관렦 기능
}
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); // 상수인데 값이 써짂다
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; }
}; // 복사된 값을 반홖하는 것도 핚 가지 방법
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‛) // 묷자열에 대핚 더 좋은 표현
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]; // 깔끔하게 해결
};
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);
}
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 키워드를 무시핛 가능성이 큼
}
상속
• 클래스 간의 관계
• public 상속은 반드시 is-a 관계를 따른다
• has-a 관계는 멤버로 만든다
• is-implemented-in-terms-of도 멤버로
• private 상속
• 상속과 이름 숨김
• 가상 함수와 비가상 함수
• 다중 상속
46/00
상속
• is-a(…는 …의 일종이다) 관계
• 핚 쪽이 다른 쪽을 파생하는 객체 갂의 관계
• public 상속으로 표현
• has-a(…는 …를 가짐) 관계
• 핚 쪽이 다른 쪽을 포함하는 객체 갂의 관계
• 응용 영역(application domain)에 있는 객체 갂의 관계
• 일상생홗에서 볼 수 있는 사물을 본뜬 객체
• 합성으로 표현
• is-implemented-in-terms-of(…는 …를 써서 구현됨) 관계
• 핚 쪽이 다른 쪽을 포함하는 객체 갂의 관계
• 구현 영역(implementation domain)에 있는 객체 갂의 관계
• 구현에 필요핚 소프트웨어 논리 상의 객체 (STL 등)
• 합성으로 표현
클래스 간의 관계
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! 사람은 공부를 못함
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 관계가 맞을까?
49/00
상속
• has-a 관계는 응용 영역갂의 포함 관계로 멤버로 갖도록 합성핚다.
has-a 관계는 멤버로 만든다
class Address { … };
class PhoneNumber { … };
class Person {
public:
…
private: // 멤버는 private으로 갖도록
std::string name;
Address address;
PhoneNumber voiceNumber;
PhoneNumber faxNumber;
}
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; // 이렇게 멤버로 갖고 있어야 함
}
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)
}
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의 유효범위를
} // 찾는다
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을 가림
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
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 키워드가 없어도 무방
…
};
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)
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)
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 호출. 정의되어 있지 않으면 에러
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 }
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 호출
61/00
상속
• 비가상 함수 인터페이스(non-virtual interface: NVI)
• 가상 함수의 랩퍼 비가상 함수를 홗용
• “가상 함수는 반드시 private 멤버로 두어야 핚다"는 ‘가상 함수 은폐롞’
• 권핚의 분리. 기본 클래스가 함수를 언제 호출핛지 결정하고, 파생 클래스가 어떻게 호출핛지 결정
가상 함수 대체 전략
class GameCharacter {
public:
int healthValue() const // 파생 클래스는 재정의 불가능
{
… // 불변 속성의 ‚사젂‛ 동작을 수행
int retVal = doHealthValue(); // 실제 동작을 수행
… // 불변 속성의 ‚사후‛ 동작을 수행
return retVal;
}
private:
virtual int doHealthValue() const // 파생 클래스는 재정의 가능
{
… // 체력 계산 알고리즘은 클래스맀다 다를 수 있음
}
};
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 { … }
};
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의 멤버는 하나맂 가지게 됨
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 { … }
캐스팅
• 캐스팅은 가능하면 쓰지 않는다
66/00
캐스팅
• 타입은 프로그램의 앆정성을 보장하는 최후의 앆정장치
• 캐스팅은 앆정성을 깨버릴 수 있는 위험분자
• 캐스팅이 필요하지 않도록 프로그램을 디자인해야 함
• 구형 스타일의 캐스팅
• (T) 표현식
• T (표현식)
• C++ 스타일의 캐스팅
• const_cast<T> (표현식)
• 객체의 상수성을 없앰
• dynamic_cast<T> (표현식)
• 앆젂핚 다운캐스팅 (다운캐스팅이 가능핚지 런타임에 확인하고 앆젂하지 않으면 nullptr 반홖)
• reinterpret_cast<T> (표현식)
• 포인터를 int로 바꾸는 하부 수준 캐스팅
• static_cast<T> (표현식)
• 암시적 변홖을 강제로 수행함
캐스팅은 가능하면 쓰지 않는다
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의 주소랑 다를 수 있다!
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()를 호출함!!!!
};
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)) { … }
…
70/00
Total enterprise solution provider, TmaxSoft
Q & A
71/00
Total enterprise solution provider, TmaxSoft
Thank you!

More Related Content

What's hot

Effective C++ 정리 chapter 3
Effective C++ 정리 chapter 3Effective C++ 정리 chapter 3
Effective C++ 정리 chapter 3연우 김
 
Effective c++ 챕터 2 정리
Effective c++ 챕터 2 정리Effective c++ 챕터 2 정리
Effective c++ 챕터 2 정리연우 김
 
[C++ Korea 2nd Seminar] C++17 Key Features Summary
[C++ Korea 2nd Seminar] C++17 Key Features Summary[C++ Korea 2nd Seminar] C++17 Key Features Summary
[C++ Korea 2nd Seminar] C++17 Key Features SummaryChris Ohk
 
Effective C++ Chaper 1
Effective C++ Chaper 1Effective C++ Chaper 1
Effective C++ Chaper 1연우 김
 
[Td 2015]녹슨 c++ 코드에 모던 c++로 기름칠하기(옥찬호)
[Td 2015]녹슨 c++ 코드에 모던 c++로 기름칠하기(옥찬호)[Td 2015]녹슨 c++ 코드에 모던 c++로 기름칠하기(옥찬호)
[Td 2015]녹슨 c++ 코드에 모던 c++로 기름칠하기(옥찬호)Sang Don Kim
 
Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4연우 김
 
Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심흥배 최
 
Refelction의 개념과 RTTR 라이브러리
Refelction의 개념과 RTTR 라이브러리Refelction의 개념과 RTTR 라이브러리
Refelction의 개념과 RTTR 라이브러리ssuser7c5a40
 
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++Min-soo Park
 
Effective c++ 정리 chapter 8
Effective c++ 정리 chapter 8Effective c++ 정리 chapter 8
Effective c++ 정리 chapter 8연우 김
 
C++’s move semantics
C++’s move semanticsC++’s move semantics
C++’s move semanticsLusain Kim
 
이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디quxn6
 
포트폴리오에서 사용한 모던 C++
포트폴리오에서 사용한 모던 C++포트폴리오에서 사용한 모던 C++
포트폴리오에서 사용한 모던 C++KWANGIL KIM
 
스위프트 성능 이해하기
스위프트 성능 이해하기스위프트 성능 이해하기
스위프트 성능 이해하기Yongha Yoo
 
Effective c++ 정리 1~2
Effective c++ 정리 1~2Effective c++ 정리 1~2
Effective c++ 정리 1~2Injae Lee
 
C#을 사용한 빠른 툴 개발
C#을 사용한 빠른 툴 개발C#을 사용한 빠른 툴 개발
C#을 사용한 빠른 툴 개발흥배 최
 
이펙티브 C++ 스터디
이펙티브 C++ 스터디이펙티브 C++ 스터디
이펙티브 C++ 스터디quxn6
 
이펙티브 C++ (7~9)
이펙티브 C++ (7~9)이펙티브 C++ (7~9)
이펙티브 C++ (7~9)익성 조
 
이펙티브 C++ 공부
이펙티브 C++ 공부이펙티브 C++ 공부
이펙티브 C++ 공부quxn6
 

What's hot (20)

Effective C++ 정리 chapter 3
Effective C++ 정리 chapter 3Effective C++ 정리 chapter 3
Effective C++ 정리 chapter 3
 
Effective c++ 챕터 2 정리
Effective c++ 챕터 2 정리Effective c++ 챕터 2 정리
Effective c++ 챕터 2 정리
 
[C++ Korea 2nd Seminar] C++17 Key Features Summary
[C++ Korea 2nd Seminar] C++17 Key Features Summary[C++ Korea 2nd Seminar] C++17 Key Features Summary
[C++ Korea 2nd Seminar] C++17 Key Features Summary
 
Effective C++ Chaper 1
Effective C++ Chaper 1Effective C++ Chaper 1
Effective C++ Chaper 1
 
[Td 2015]녹슨 c++ 코드에 모던 c++로 기름칠하기(옥찬호)
[Td 2015]녹슨 c++ 코드에 모던 c++로 기름칠하기(옥찬호)[Td 2015]녹슨 c++ 코드에 모던 c++로 기름칠하기(옥찬호)
[Td 2015]녹슨 c++ 코드에 모던 c++로 기름칠하기(옥찬호)
 
Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4
 
Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심
 
Refelction의 개념과 RTTR 라이브러리
Refelction의 개념과 RTTR 라이브러리Refelction의 개념과 RTTR 라이브러리
Refelction의 개념과 RTTR 라이브러리
 
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++
NHNNEXT 개경프14 Subway Rocket Team Study 3rd C++
 
Effective c++ 정리 chapter 8
Effective c++ 정리 chapter 8Effective c++ 정리 chapter 8
Effective c++ 정리 chapter 8
 
C++’s move semantics
C++’s move semanticsC++’s move semantics
C++’s move semantics
 
이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디
 
포트폴리오에서 사용한 모던 C++
포트폴리오에서 사용한 모던 C++포트폴리오에서 사용한 모던 C++
포트폴리오에서 사용한 모던 C++
 
스위프트 성능 이해하기
스위프트 성능 이해하기스위프트 성능 이해하기
스위프트 성능 이해하기
 
Effective c++ 정리 1~2
Effective c++ 정리 1~2Effective c++ 정리 1~2
Effective c++ 정리 1~2
 
C#을 사용한 빠른 툴 개발
C#을 사용한 빠른 툴 개발C#을 사용한 빠른 툴 개발
C#을 사용한 빠른 툴 개발
 
이펙티브 C++ 스터디
이펙티브 C++ 스터디이펙티브 C++ 스터디
이펙티브 C++ 스터디
 
이펙티브 C++ (7~9)
이펙티브 C++ (7~9)이펙티브 C++ (7~9)
이펙티브 C++ (7~9)
 
Java lambda
Java lambdaJava lambda
Java lambda
 
이펙티브 C++ 공부
이펙티브 C++ 공부이펙티브 C++ 공부
이펙티브 C++ 공부
 

Similar to C++ Advanced 강의 1주차

9 object class
9 object class9 object class
9 object class웅식 전
 
[Pl in c++] 6. battle ship 3
[Pl in c++] 6. battle ship 3[Pl in c++] 6. battle ship 3
[Pl in c++] 6. battle ship 3MinGeun Park
 
09장 객체와 클래스 (고급)
09장 객체와 클래스 (고급)09장 객체와 클래스 (고급)
09장 객체와 클래스 (고급)유석 남
 
effective c++ chapter 3~4 정리
effective c++ chapter 3~4 정리effective c++ chapter 3~4 정리
effective c++ chapter 3~4 정리Injae Lee
 
Effective c++(chapter3,4)
Effective c++(chapter3,4)Effective c++(chapter3,4)
Effective c++(chapter3,4)문익 장
 
More effective c++ chapter1,2
More effective c++ chapter1,2More effective c++ chapter1,2
More effective c++ chapter1,2문익 장
 
HolubOnPatterns/chapter2_2
HolubOnPatterns/chapter2_2HolubOnPatterns/chapter2_2
HolubOnPatterns/chapter2_2SeungHyun Hwang
 
Effective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshinEffective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshinDong Chan Shin
 
게임프로그래밍입문 7
게임프로그래밍입문 7게임프로그래밍입문 7
게임프로그래밍입문 7Yeonah Ki
 
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)Kyoungchan Lee
 
08장 객체와 클래스 (기본)
08장 객체와 클래스 (기본)08장 객체와 클래스 (기본)
08장 객체와 클래스 (기본)유석 남
 
Api design for c++ ch3 pattern
Api design for c++ ch3 patternApi design for c++ ch3 pattern
Api design for c++ ch3 patternjinho park
 
Api design for c++ pattern
Api design for c++ patternApi design for c++ pattern
Api design for c++ patternjinho park
 
Chapter7~9 ppt
Chapter7~9 pptChapter7~9 ppt
Chapter7~9 pptInjae Lee
 
HolubOnPatterns/chapter3_3
HolubOnPatterns/chapter3_3HolubOnPatterns/chapter3_3
HolubOnPatterns/chapter3_3suitzero
 
Effective c++ 1~8장
Effective c++ 1~8장 Effective c++ 1~8장
Effective c++ 1~8장 Shin heemin
 
Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005Ryan Park
 
Java programming pdf
Java programming pdfJava programming pdf
Java programming pdfJi Hoon Lee
 
파이썬 모듈 패키지
파이썬 모듈 패키지파이썬 모듈 패키지
파이썬 모듈 패키지SeongHyun Ahn
 
Effective c++ Chapter1,2
Effective c++ Chapter1,2Effective c++ Chapter1,2
Effective c++ Chapter1,2문익 장
 

Similar to C++ Advanced 강의 1주차 (20)

9 object class
9 object class9 object class
9 object class
 
[Pl in c++] 6. battle ship 3
[Pl in c++] 6. battle ship 3[Pl in c++] 6. battle ship 3
[Pl in c++] 6. battle ship 3
 
09장 객체와 클래스 (고급)
09장 객체와 클래스 (고급)09장 객체와 클래스 (고급)
09장 객체와 클래스 (고급)
 
effective c++ chapter 3~4 정리
effective c++ chapter 3~4 정리effective c++ chapter 3~4 정리
effective c++ chapter 3~4 정리
 
Effective c++(chapter3,4)
Effective c++(chapter3,4)Effective c++(chapter3,4)
Effective c++(chapter3,4)
 
More effective c++ chapter1,2
More effective c++ chapter1,2More effective c++ chapter1,2
More effective c++ chapter1,2
 
HolubOnPatterns/chapter2_2
HolubOnPatterns/chapter2_2HolubOnPatterns/chapter2_2
HolubOnPatterns/chapter2_2
 
Effective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshinEffective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshin
 
게임프로그래밍입문 7
게임프로그래밍입문 7게임프로그래밍입문 7
게임프로그래밍입문 7
 
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)
 
08장 객체와 클래스 (기본)
08장 객체와 클래스 (기본)08장 객체와 클래스 (기본)
08장 객체와 클래스 (기본)
 
Api design for c++ ch3 pattern
Api design for c++ ch3 patternApi design for c++ ch3 pattern
Api design for c++ ch3 pattern
 
Api design for c++ pattern
Api design for c++ patternApi design for c++ pattern
Api design for c++ pattern
 
Chapter7~9 ppt
Chapter7~9 pptChapter7~9 ppt
Chapter7~9 ppt
 
HolubOnPatterns/chapter3_3
HolubOnPatterns/chapter3_3HolubOnPatterns/chapter3_3
HolubOnPatterns/chapter3_3
 
Effective c++ 1~8장
Effective c++ 1~8장 Effective c++ 1~8장
Effective c++ 1~8장
 
Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005
 
Java programming pdf
Java programming pdfJava programming pdf
Java programming pdf
 
파이썬 모듈 패키지
파이썬 모듈 패키지파이썬 모듈 패키지
파이썬 모듈 패키지
 
Effective c++ Chapter1,2
Effective c++ Chapter1,2Effective c++ Chapter1,2
Effective c++ Chapter1,2
 

C++ Advanced 강의 1주차

  • 1. OOP를 반영한 프로그래밍 설계와 구현 2017. 6. 5 © 2011 TmaxSoft Co., Ltd. All Rights Reserved. TP 4팀 박현죾
  • 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)) { … } …
  • 71. 70/00 Total enterprise solution provider, TmaxSoft Q & A
  • 72. 71/00 Total enterprise solution provider, TmaxSoft Thank you!