SlideShare a Scribd company logo
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 Summary
Chris 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 semantics
Lusain 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~2
Injae Lee
 
C#을 사용한 빠른 툴 개발
C#을 사용한 빠른 툴 개발C#을 사용한 빠른 툴 개발
C#을 사용한 빠른 툴 개발
흥배 최
 
이펙티브 C++ 스터디
이펙티브 C++ 스터디이펙티브 C++ 스터디
이펙티브 C++ 스터디
quxn6
 
이펙티브 C++ (7~9)
이펙티브 C++ (7~9)이펙티브 C++ (7~9)
이펙티브 C++ (7~9)익성 조
 
Java lambda
Java lambdaJava lambda
Java lambda
Hyosang Hong
 
이펙티브 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_dcshin
Dong Chan Shin
 
게임프로그래밍입문 7
게임프로그래밍입문 7게임프로그래밍입문 7
게임프로그래밍입문 7
Yeonah 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++ pattern
jinho park
 
Chapter7~9 ppt
Chapter7~9 pptChapter7~9 ppt
Chapter7~9 ppt
Injae 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 - xp2005
Ryan Park
 
Java programming pdf
Java programming pdfJava programming pdf
Java programming pdf
Ji 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!