cse.snu.ac.kr

Download Report

Transcript cse.snu.ac.kr

C++ 프로그래밍 및 실무
2014. 6. 17 (화)
유승현
1
강의 계획
• 세션1
– C++ 문법
• 메모리 관련, 클래스, 템플릿
• STL, 예외처리, …
• 세션2
– 개발 실무 기초
• 소스코드 형상 관리(svn, git)
• 디버깅 테크닉
• 코드 의존성 줄이기
솔직히 다할 수 있을지…
2
Why C++?
• 다른 프로그래밍 언어들의 대두
– Java, C#, Python, JavaScript, PHP, Object-C, ...
• 웹, 스마트폰 선호, 상대적으로 낮아진 일반 application
• Native 언어
– 장점: 빠른 속도 & 완전한 컨트롤
– 단점: 상대적으로 낮은 생산성(귀찮음…), 이식성(컴파일 다시
해야함)
• 중요 모듈 및 성능이 중요한 프로젝트는 C++로 작성
• 계속 진화 중인 언어 표준 C++98 > C++11 > C++14
3
C++ Hello World 프로그램
#include "stdafx.h"
#include <iostream>
using namespace std;
#include "stdafx.h"
#include <stdio.h>
int _tmain(int argc, _TCHAR* argv[])
{
cout << "Hello World" << endl;
int x;
cin >> x;
int _tmain(int argc, _TCHAR* argv[])
{
printf("Hello World\n");
int x;
scanf("%d", &x);
return 0;
}
return 0;
}
C에서의 구현
• 다른점? #include <iostream>? using namespace? cout? >>? <<?
4
C++: Multi-paradigm Programming Language
Functional
Programming
C++
Procedural
Programming
Object-Oriented
Programming
Generic
Programming
Template
Metaprogramming
5
C++: Procedural Programming
• 프로그램의 상태를 직접적으로 변화시키는 명령들로의
구성
• 처리해야 할 작업을 순차적으로 나열 – 전통적인 방식
• 특성
Direct assignments, Common data structures, Global variables,
Local variables, Iteration, Modularization
6
C++: Object-Oriented Programming
• 객체(Object) = 데이터(특성; Attributes) + 코드(행동 양식; Behavior)
• 프로그램의 상태를 ‘객체’의 미리 정의된 Behavior를 통해서만
조작함
• GUI로 인해 엄청나게 증가한 소프트웨어의 복잡성을 다루기 위해
‘모듈화’ 또는 ‘재사용성’ 강조
• 특성
Objects, Classes, Methods, Message Passing, Information Hiding,
Data Abstraction, Encapsulation, Polymorphism, Inheritance,
Serialization
7
C++: Functional Programming
• 프로그램 내부에 상태(state)가 형성되는 것을 피하기
위해 프로그램을 수학적 함수의 결합으로 나타낸다.
• 병렬 처리/ 분산 환경: 필수적으로 다가온 동시성 다루기
– ‘부작용’이 없어야 함
• 특성
Lambda calculus (C++11)
Higher order functions
Closures
Recursion
No side effects
C
Function pointer
C++
Function object
C++11
Lambda function
Functional Programming의 지원
8
C++: Generic Programming
• Type에 관계없이 일반화된 코드를
작성하고 싶은 마음
• 가능한 원소 type마다 고유한 List
Type을 일일이 만드는 것은 비효율적
9
C++: Template Metaprogramming
• 템플릿을 사용하여 컴파일러가 프로그램 코드를 생성
• 런타임 시점이 아닌 컴파일 시점에 실행
• 보통은 더 ‘안전’하거나 ‘효율적’인 Generic
Programming을 위해 사용
• traits  type에 따라 다른 처리
• 사실 ‘계산’ 목적으로는 잘 쓰지 않음 – 사용자 입력을 못 10
C++: Template Metaprogramming
• C++의 template 기능은 또 다른 한 언어로 봐도 무방…
11
Heap 영역 vs Stack 영역
Heap 영역
높은 메모리 주소
낮은 메모리 주소
Heap
영역
• 동적으로 메모리 공간을 할당(특정 메모리 공간을
예약)
• 생명 주기는 프로그래머가 알아서 관리
미사용
Stack 영역
Stack
영역
• 지역변수/ 매개변수
• 함수 호출에 필요한 정보: Call frame(Return Address,
…)
• 생명 주기는 Scope { } 에 의해 결정됨
운영체제가 프로세스(Process)에 ‘가상메모리’ 영역을 제공
각 프로세스는 ‘독립적’ 주소 체계를 가질 수 있음
12
지역변수의 생명 주기
• Scope { } 에 의해 정해짐. Scope가 시작될 때 ‘할당’, 끝날 때 ‘해제’
function foo의
scope 시작
Nested scope의 시작: Local3, Local4의 공간
할당
// for문에서의 scope 시작
// for문의 scope의 끝
Nested scope의 끝 Local3, Local4 해제
Local1, Local2, 그리고 arg1, arg2 해제
• 컴파일러는 scope 안에서 사용될 지역변수의 크기를 알고 있음
• ‘할당’ / ‘해제’라고 표현을 했지만, Stack Pointer 위치만 바꿔줄 뿐!
• int array[N]; 과 같은 문법이 불가능한 이유임(컴파일러가 크기를
파악할 수 없어 stack 공간에 할당할 수 없음)
13
Memory Management in C++
• new, delete 연산자의 도입
C에서의 heap memory 할당/해제
C++에서는 new, delete 연산자 사용
14
Memory Management in C++
• delete vs delete[]
– new / delete
– new [] / delete[] 짝을 반드시 맞춰야 함
– 메모리 layout이 다르기 때문!
header
힙 공간에 대한
정보
주의! 마찬가지로 malloc으로 생성한 것을 delete, new로 생성한 것을
free로 해제해서는 안된다.
15
Memory Management in C++
• 언어 차원에서 지원하는 메모리 할당/해제 연산자이므로
별도의 include가 필요없다.
• New/delete는 객체가 생성, 소멸할 때
생성자(constructor; ctor)와 소멸자(destructor; dtor)를
각각 호출한다.
• 연산자 오버로딩 기능을 사용하면 new와 delete를
재정의할 수도 있음
– malloc이나 free를 재정의하는 것은 사실상 불가능함
– Memory Leak을 추적하기 위해 new가 불릴 때마다 기록을
하거나
– 성능 향상을 위해 프로그래머가 효율적인 메모리 할당/해제
알고리즘을 적용할 수도 있음(Memory Allocator 구현)
16
Pass-by-value
• 함수의 매개변수가 전달될 때 기본적으로 항상
‘복사’하여 전달한다. 같은 ‘값’을 전달한다는 의미에서
pass-by-value라고 함
• 두 변수의 값을 서로 바꾸는 swap 함수 구현
void swap_fail(int a, int b)
{
int t = b;
b = a;
a = t;
}
• 그러나 매개변수로 들어온 a, b는 원래 변수와는 같은
값만을 가질 뿐 사실상 전혀 관계 없는 다른 변수
17
Pass-by-pointer
• Pass-by-pointer는 메모리 주소를 직접 전달한다.
• 그 결과 함수 안에서 원래 변수의 값을 조작할 수 있게 됨
• Swap 함수의 매개변수 a와 b은 m과 n의 주소를 원하므로
변수의 주소를 알아내는 &을 변수 앞에 붙여야 함
• 덩치가 큰 변수의 전달 시 ‘복사’에 따른 오버헤드를
없애기 위해
18
Pass-by-reference
• Reference 변수를 사용하면 이 과정을 컴파일러가
알아서 해줌
• a와 b의 ‘값’을 전달하는 것이 아니라 a의 주소와 b의
주소를 전달한다. 함수 안에서는 일반 변수 다루듯이
사용할 수 있음 // 오류! 포인터와 달리 Reference 변수는 반드시 한
변수로 초기화되어야
한다.
• Reference를 사용하기
위해 반드시
원래 변수가 있어야
함
19
Pointer vs Reference 비교
• Pass-by-pointer와 내부적인 동작 차이는 없음
• 다만 pass-by-pointe에서는 0, 즉 null pointer가 들어갈
수도 있음. 그러나 pass-by-reference에서는 허용되지
않음
// Null pointer 전달 가능
// 허용되지 않음( ‘상수’의 주소를 알아올 수 없기
때문
)
error C2664: 'swap2' : cannot convert
parameter
1 from 'int' to 'int &'
// 사실 억지로 전달할 수 있긴 함…
20
Const Reference
• 배열의 전체 합을 구하는 함수
• 만약 pass-by-value로 전달한다면?
• arr 전체가 또 한번 복사  비효율
• Const Modifier를 쓰지 않았다면?
• get_sum 함수 내에서 arr를 건드릴 수 있음  의도치 않은 결과 초래
가능성
21
Const Modifier
// compiler error = expression must be a modifiable value
char* str1 = "hello1";
str1[0] = 'y'; // access violation
str1 = "world1";
const char* str2 = "hello2";
str2[0] = 'y'; // compile error
str2 = "world2";
char* const str3 = "hello3";
str3[0] = 'y';
str3 = "world3"; // compile error
const char* const str4 = "hello4";
str4[0] = 'y'; // compile error
str4 = "world4"; // compile error
22
OOP Keywords
•
•
•
•
Encapsulation
Information Hiding
Inheritance
Polymorphism
23
Encapsulation
• 객체의 속성(Data fields)와 행위(Methods)를 하나로 묶음
• 프로그램이란?
– 결국 데이터를 특정한 방식에 의해 처리하는 것을 의미
– 객체에는 데이터와 행동 양식이 묶여 있음
– 즉, 하나의 부품으로 활용될 수 있음
그림 출처 http://java-answers.blogspot.kr/2012/01/oops-and-its-concepts-or-properties-in_06.html
24
Information Hiding
• 지나친 데이터 노출은 혼란을 가중시킬 수 있음
– (ex) Brand, Speed, Gear, Color 이 노출되었다면?
• 외부와 상호작용하기 위한 인터페이스 부분만 남기고
나머지는 내부에서 처리
– (ex) 가속, 기어 변속, 브레이크
• 정의된 객체는 ‘잘 동작한다’는 가정하에 실제로 사용을
어떻게 하면 되는가가 관심사. 객체 내부의 세부적인
동작이 어떻게 되는지는 알고 싶지 않음
• 또 외부에서 함부로 객체의 상태를 바꿀 수 없도록 안전
장치를 만들어놓은 것으로 볼 수도 있음
25
상속(Inheritance)
Parent class(super class; base class)
계좌
소유자
계좌번호
잔액
입금하기
출금하기
• Savings Account와 Checking
Account는 모두 공통적으로 ‘계좌’의
속성과 행동 양식을 가진다.
• 새로 구현할 것이 아니라 ‘계좌’의
속성 및 행동 양식을 그대로
물려받는다.
Savings Account
Checking Account
이자율
수수료
이자 지급하기
수수료 뺏어가기
• Child class(subclass; derived class)
26
Polymorphism
Shape
draw()
getSize()
Rectangle
draw()
getSize()
Circle
draw()
getSize()
Triangle
draw()
getSize()
• Polymorphism (다형성)은 다른 type에 대해 같은 interface를 제공한다.
• 위의 예는 Sub Typing이라고도 부름. Rectangle, Circle, Triangle은
Shape의 draw와 getSize라는 인터페이스를 상속받지만 각자 하는
일이 다르다.
• 다형성의 장점은 객체를 한꺼번에 다룰 수 있다는 점. Shape로
통일되어 있지 않을 때 모든 Rectangle, Circle, Triangle의 넓이의 합을
구하려면?
• Function Overloading도 넓게는 Polymorphism의 개념에 포함됨
27
Class
• C의 struct
– 관련 있는 데이터들을 하나의 type 으로 묶기 위한 시도
– (ex) 계좌 레코드  {소유자, 계좌번호, 잔액}
• C++의 class
– C의 struct 개념을 확장하여 function까지도 가질 수 있는 type
– (ex)C에서 입금하기를 구현한다면 계좌 레코드를 접근하는
함수가 필요하다. 아예 class가 function을 포함한다면?
28
Class
• Member variable (or member data)
– 클래스 내의 변수
• Member function (method)
– 클래스 내의 함수: 객체가 무슨 일을 하는지 정의할 수 있다!
• Instance
– 클래스 자체는 type일 뿐
– 클래스에 의해 만들어진 변수(메모리에 실재)
“내가 그의 이름(type)을 불러 주었을 때
그는 나에게로 와서
꽃(instance)이 되었다"
29
Class 선언하기
class ClassName : public BaseClassName
상속이 필요한 경우에만
{
public:
Member Variable
SomeType publicMemberVar;
void foo(){
Member Function
/* some defines */
}
void foo2(); /* elsewhere */ 나중에 다른 곳에서 정의해줘야 함
protected:
SomeType protectedMemberVar;
private:
SomeType privateMemberVar;
};
• 앞으로 ClassName이라는 type을 추가한다는 의미임!
• 실제로 클래스를 사용하기 위해선 인스턴스(instance)를 생성해야함
30
Class에서 선언한 멤버 함수는?
• 클래스 자체에서 멤버함수를 정의하는 경우
– Inline 함수 등을 염두에 두고 매우 짧은 함수(단순히 멤버 변수
값을 리턴해주는 함수)
• 클래스 정의는 header 파일(*.h), 클래스 내의 함수
정의는 source 파일(*.cpp)에 넣는다.
void ClassName::foo2(){
/* defines */
}
31
Access modifiers
• 클래스 내의 멤버 변수나 멤버 함수를 외부에서 접근할 수
있는지 여부를 지정
– Public
• C에서의 struct처럼 외부에서 (클래스 밖에서) 마음대로 접근할 수 있음
– Private
• 클래스 안의 메소드(멤버 함수)에서만 접근 가능
– Protected
• 외부에서 보기에는 private로 접근이 불가능하지만, 상속 관계하에서는
public으로 접근이 가능함
• Access Modifier를 따로 지정하지 않으면
– Class는 자동으로 private
– Struct는 자동으로 public 으로 간주
– C++에서 struct와 class의 차이는 default access modifier의 차이 뿐
32
this 키워드
• 클래스의 멤버 함수에서 자기 자신을 참조하고 싶을 때
• this의 type은 해당 클래스의 포인터 타입이라고 볼 수 있음
class ClassName{
public:
void PrintAddress(){
std::cout << "this = " << this << std::endl;
}
};
ClassName name;
std::cout << "Address of name = " << &name << std::endl; // Address of name = 0038FD6F
name.PrintAddress(); // this = 0038FD6F
• 인스턴스가 다르면 당연히 다른 값이어야 함
• 멤버 변수의 이름과 지역 변수의 이름이 겹칠 때 모호성을 해소하기
위해 사용할 수도 있음
33
Static Member Variable
• 클래스는 type을 의미하고 각 member variable은
독립적임
• 그러나 Static 키워드를 추가하면 클래스 type에 대해
class CountableClass {
public:유일한 변수를 할당할 수 있음
CountableClass() { std::cout << "인스턴스 생성 " << this << std::endl; count++; }
~CountableClass() { std::cout << "인스턴스 소멸 " << this << std::endl; count--; }
int getCount() const { return count; }
private:
static int count;
};
int CountableClass::count = 0;
int _tmain(int argc, _TCHAR* argv[])
{
CountableClass instanceA;
{
CountableClass instanceB;
std::cout << instanceB.getCount() << std::endl;
}
std::cout << instanceA.getCount() << std::endl;
return 0;
}
34
Const Member Variable
class CountableClass {
public:
CountableClass() : typeCode(456) { /* 생성자 */ }
private:
static const int staticTypeCode = 123;
const int typeCode;
};
• 멤버 변수의 성격이 상수일 때 const 키워드를 붙인다.
• Static 키워드를 붙이면 바로 초기화 가능(type이 int일 때만)
• 일반 const member variable은 생성자의 초기화 리스트에서 초기화
35
Mutable Member Variable
• Const 한정자가 붙은 함수는 member variable의 값을 수정할 수 없다!
class CountableClass {
public:
/* 생략 */
int getCount() const {
callCount++;
return count;
}
private:
static int count;
int callCount;
};
컴파일 에러: Expression must be modifiable L-Value
• 그렇다고 getCount()에 const를 뺄 수도 없음
• Member variable 앞에 mutable 넣으면 해결!
mutable int callCount;
36
생성자(Constructor)
초기화 리스트
class Contact{
public:
Contact() : name("undefined"), age(0) {}
Contact(const std::string& name_, int age_)
: name( name_ ), age( age_ ) {}
private:
매개변수를 가질
std::string name;
수 있음
int age;
};
• 클래스와 같은 이름을 가지는 함수(리턴 type은 없음- void가 아님)
• 객체가 생성될 때 자동으로 호출된다
• new 연산자에 의해 생성되거나(malloc은 안됨)
• Local variable로 할당되는 순간
• 외부에서 접근 가능해야 하므로 반드시 public
• 매개변수가 없는 Contact() 가 기본 생성자(default constructor)
37
멤버 초기화 리스트(Member Initialization
List)
• 초기화 리스트의 순서는 아무 상관 없다.
class Type1 { public: Type1(int a){ std::cout << "Type 1 초기화" << std::endl; } };
class Type2 { public: Type2(int b){ std::cout << "Type 2 초기화" << std::endl; } };
class Type3 { public: Type3(int c){ std::cout << "Type 3 초기화" << std::endl; } };
class MemberInitializationTest {
public:
MemberInitializationTest()
: type1(0)
, type2(0)
, type3(0)
{
}
private:
Type2 type2;
Type3 type3;
Type1 type1;
} test;
• 실행 결과는?
• Class 안에서 정의된 순서에 의해 초기화가 진행됨
38
레퍼런스 멤버의 초기화
class Manager { /* some definitions */ };
class TestObject {
public:
private:
Manager& manager;
} testObject;
멤버 변수로 reference 변수 가능함
단, 초기화 리스트에서 초기화하지 않으면
컴파일 에러(no appropriate default
constructor available)
class TestObject {
public:
TestObject(Manager& manager_) : manager(manager_) {}
private:
Manager& manager;
} testObject(Manager());
실제로는 올바른 객체를 넣어야겠죠?
39
소멸자(Destructor)
• 객체가 소멸될 때 자동으로 불리는 함수
– Delete 연산자에 의해
– scope가 끝나 local variable의 생명 주기가 끝났을 때
•
•
•
•
클래스 이름 앞에 ~가 붙는다
역시 리턴 타입 없음
매개변수는 없다(사용자가 부르는 함수가 아님)
Access modifier는 public이어야 함
class Base {
public:
/* … */
~Base(){
std::cout << "Base 소멸" << std::endl;
}
};
40
상속된 클래스에서의 생성자/ 소멸자
class Base {
public:
Base(){
std::cout << "Base 생성" << std::endl;
}
~Base(){
std::cout << "Base 소멸" << std::endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Derived instance;
return 0;
}
실행 결과
class Derived : public Base {
public:
Derived()
{
std::cout << "Derived 생성" << std::endl;
}
~Derived(){
std::cout << "Derived 소멸" << std::endl;
}
};
41
복사 생성자(Copy Constructor)
• 필요성 – Implicit Copy의 문제점
class MyString
{
public:
void allocate(){
pointer = new char[16]; // initial buffer
}
/* for debug */
void setPointer(char* pointer_) { pointer = pointer_; }
char* getPointer() const { return pointer; }
private:
char* pointer;
};
42
복사 생성자(Copy Constructor) [2]
• 내부의 포인터 주소 자체가 복사됨
MyString str1;
MyString str2;
* str1 그리고 str2가 같은 주소를 가리키므로 str1을
수정하면 str2에도 반영됨
str1.allocate();
str2 = str1; // Implicit Copy
std::cout << "str1.getPointer() = " << (int)str1.getPointer()
<< std::endl;
std::cout << "str2.getPointer() = " << (int)str2.getPointer()
<< std::endl;
str1
• pointer
H E L L O W O R L D \0
str2
• pointer
43
복사 생성자(Copy Constructor) [3]
class MyString
{
public:
MyString() : pointer(nullptr) {}
MyString(const MyString& copy_from_me){
this->allocate(); // 적당한 공간 할당 후
// copy_from_me 에서부터 문자열 복사하기
}
… 생략 …
MyString str1;
str1.allocate();
MyString str2(str1);
44
복사 대입 연산자 =
MyString& operator=(const MyString& copy_from_me){
this->allocate(); // 적당한 공간 할당 후
// copy_from_me 에서부터 문자열 복사하기
return *this; // 자기 자신을 리턴함
}
(생각해봐야 할 점)
self-assignment
(ex) str1 = str1;
45
Getter and Setter
– 노가다?
연락처 Class
Member Variables
• 이름
string getName() const
void setName(const string& name)
• 그러나 멤버 변수가 외부로
노출되어 있을 시,
• 값이 한번 바뀌면 언제
바뀌었는지 추적하기가 너무
어렵다.
• 전화번호
string getPhone() const
void setPhone(const string&
phone)
• Getter/ Setter 만들면, 언제
어디서 값이 바뀌는지
추적이 용이
• 이메일
string getEmail() const
void setEmail(const string& email)
46
상속 범위
클래스내
friend 함수 내
자식 클래스 내
클래스
외부(main등)
public
O
O
O
protected
O
O
O
private
O
O
X
O
X
X
• 상속 받을 때 보통 부모 클래스의 메소드를 접근하게 되므로 public
상속을 주로 사용한다.
• Is-a 관계 : A savings account is an account.
• Private 상속
• Is-implemented-in-terms-of
• ‘구현’을 편하게 할 목적으로 부모 클래스를 상속받는 경우엔
부모 클래스의 메소드가 외부로 노출되어선 안되므로
• 경험상, Private 상속 보다는 멤버 변수로 부모 클래스를 들고 있는
편이 나았음
47
상속 시 이름이 겹칠 때
class ConflictBase {
public:
int X;
};
class ConflictDerived : public ConflictBase {
public:
int X;
void PrintX(){
std::cout << X << std::endl;
}
void PrintBaseX(){
std::cout << ConflictBase::X << std::endl;
}
};
같은 이름이 있으면 자기 자신의 멤버 변수가 우선하지만
부모 클래스의 X가 필요한 경우는 위처럼 범위를 지정해줄
수 있다.
48
상속 시 이름이 겹칠 때(2)
ConflictDerived d;
d.X = 1;
d.PrintX();
d.PrintBaseX();
출력 결과>
1
-858993460 (쓰레기값)
1
2
ConflictBase& base(d);
base.X = 2;
d.PrintX();
d.PrintBaseX();
• 자식 클래스를 부모 클래스로 캐스팅해서 쓸 수도 있다.
• 반대로는 하면 안됨(자식 클래스의 크기 >= 부모 클래스의 크기)
49
가상함수(Virtual Function)
• A function or method whose behavior can be overridden
with an inheriting class by a function with the same
signature
• 부모 클래스와 자식 클래스가 같은 형태의 함수(리턴값,
매개변수 목록, 함수 이름)을 갖고 있을 때, 자식
클래스의 함수가 실행된다.
50
가상함수(Virtual Function) [2]
class Shape {
public:
double getSize(){
return 0.0f;
}
};
class Rectangle : public Shape {
public:
Rectangle(double width, double height)
: width(width), height(height) {}
double getSize(){ return width * height; }
private:
double width;
double height;
};
• Rectangle을 Shape로도 다루고 싶은데...
Rectangle rectangle(10,20);
std::cout << "Rectangle 1 = " << rectangle.getSize()
<< std::endl;
Shape& shape(rectangle);
std::cout << "Shape (Rectangle 1) = " << shape.getSize()
<< std::endl;
class Circle : public Shape {
public:
Circle(double radius) : radius(radius) {}
double getSize(){ return 3.141592 * radius * radius; }
private:
double radius;
};
• 원래 의도는
Rectangle::getSize()를
호출하는 것이었지만,
Shape::getSize()가
• Shape *ptr = &rectangle; ptr->getSize(); 도 마찬가지
• 게다가 ptr에는 circle이 대입될 수도 있고 현 상황에서는 실행 중에
타입을 알 수 있는 방법이 전혀 없기 때문이다.
51
가상함수(Virtual Function) [3]
• 함수 앞에 virtual 키워드를 붙인다.
class Shape {
public:
virtual double getSize(){
return 0.0f;
}
};
• virtual 이 있을 때와 없을 때 무엇이 다른 걸까?
멤버 변수 외에 __vfptr 이라는 값이 들어있다. 이는 virtual table의
주소를 가리키는 값으로 4byte 의 추가 공간을 필요로 한다. __vfptr을
따라가보면 Rectangle::getSize()이 지정되어 있다.
52
Virtual Table이란?
• type별로 무슨 함수가 실행되어야
할지 각 함수의 주소를 표로 나타낸
것
• 객체의 메소드를 실행할 때
static한 type에 의존하지 않고
객체에 type 정보를 추가로 둔다.
• D1에서 function1을 부를 때
• 바로 부르는 것이 아니라
__vptr를 통해 function1()의
시작 주소를 얻는다
• Function2를 부를 때
• Function2는 정의가 되어
있지 않아 virtual table에
base의 function2 주소로
따라가도록 되어있다.
오버헤드: __vptr 크기(pointer 크기)
vtable에서 함수 주소 읽어오기
그림 출처 http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/
53
Polymorphism의 응용
• Abstract Class (혹은 Interface Class)
class Shape {
public:
Shape() {}
virtual double getSize() = 0;
virtual void draw() = 0;
};
class Rectangle : public Shape {
public:
Rectangle(double width, double height) : width(width),
height(height) {}
double getSize(){ return width * height; }
void draw() { std::cout << "The rectangle is drawed :
" << width << "x" << height << std::endl; }
private:
double width;
double height;
};
class Circle : public Shape {
public:
Circle(double radius) : radius(radius) {}
double getSize(){ return 3.141592 * radius * radius; }
void draw() { std::cout << "The circle is drawed : "
<< radius << std::endl; }
private:
double radius;
};
for ( int i = 0; i < _countof(shapes); ++i )
{
std::cout << shapes[i]->getSize
<< std::endl;
shapes[i]->draw();
}
54
소멸자와 virtual
• Shape와 Rectangle, Circle에 모두 소멸자 함수를
for ( int i 때
= 0; i < _countof(shapes); ++i ) {
정의했을
delete shapes[i];
}
• Rectangle, Circle 소멸자 코드는 실행되지 않음
• 소멸자가
virtual
이 아니므로 virtual table 에 없음
virtual ~Shape()
{
std::cout << "Shape 파괴 " << (int)this << std::endl;
}
• Shape의 소멸자가 실행되기 전 Rectangle 소멸자,
Circle 소멸자도 실행됨을 알 수 있다.
• 이와 같이 virtual 을 쓰지 않으면 자식 클래스의
소멸자가 실행되지 않으므로 상속을 고려한다면
항상 소멸자에는 virtual을 넣도록 한다.
55
C++ Template
•
•
•
•
C++의 강력한 기능 중 하나 – 그러나 난해하기도 한 부분
Generic Programming
Template Metaprogramming
코드를 컴파일러가 ‘자동’으로 생성해준다.
• 종류
– 함수 템플릿
– 클래스 템플릿
56
Function Template
• Function Overloading 만으론 한계가 있다.
• Swap 함수의 구현을 생각해볼 때,
– Type 별로 Swap 함수를 일일이 구현한다는 것은 불가능함
– 그렇다고 Type에 따라 구현하는 방식이 달라지지도 않음
• 일반화된 표현 방식의 필요성
– 특정 Type에 대한 대입 연산자만 잘 정의되어 있다면 no problem
57
Function Template [2]
template< typename value_type >
void Swap( value_type& t1, value_type& t2 ){
value_type t = t1;
t1 = t2;
t2 = t;
}
Rectangle rec1(10,20);
Rectangle rec2(30,40);
rec1.draw();
rec2.draw();
Swap(rec1, rec2);
rec1.draw();
rec2.draw();
• value_type에 = 연산만 잘 정의되어 있으면 어떤
type에 대해서도 swap 가능
• 컴파일러의 타입 추론 -Swap<Rectangle>(rec1, rec2); 으로 불러도
되나 Swap(rec1, rec2); 도 문제 없음
• 사용된 모든 종류의 value_type에 대한 코드를
컴파일러에서 생성해준다. (Implicit
Instantiation)
58
Function Template [3]
* 캐스팅 함수의 정의
template< typename To, typename From >
To my_cast( const From& from ){
std::cout << typeid(From).name() << " to " << typeid(To).name()
<< std::endl;
return static_cast<To>(from);
}
char a = 'a';
int a_casted = my_cast<int>(a);
컴파일러가 typename From은 알아서
추론해줌
59
Function Template [4]
• 템플릿 특수화(Template Specialization)
– 어떤 type에 대해서는 특별하게 처리하고 싶은 경우가 있을 때
template<> long my_cast( const char& from ){
std::cout << "[long] 재정의된 캐스팅" << std::endl;
return static_cast<long>(from);
}
* char -> long 은 다르게 처리하고 싶을 때(…)
char a = 'a';
int a_casted = my_cast<int>(a);
long b_casted = my_cast<long>(a);
60
Function Template [5]
* 템플릿 특수화의 우선 순위
test t;
t.Test( 1 );
t.Test<char>( (char) 1 );
t.Test( (char) 1 );
1. 일반 함수가 가장 우선
2. 템플릿 특수화에 의한 함수는 그 다음
3. 마지막으로 템플릿 함수
61
Class Template
• Function Template와 마찬가지로 클래스에서도
template을 제공
template<typename T>
class MyStack{
public:
void Push(const T& item);
T Pop() { return T(); }
private:
T* data;
};
보통 template 코드는 클래스 선언
내에 정의되는 경우가 많다.
* 클래스 템플릿 선언과 별도로 정의할 수도 있다.
template<typename T> void MyStack<T>::Push(const T& item){
std::cout << "push " << item << std::endl;
}
MyStack<int> intStack;
MyStack<double> doubleStack;
MyStack<Shape*> shapePointerStack;
intStack.Push(123);
doubleStack.Push(456.0);
shapePointerStack.Push(nullptr);
62
Template 관련 에러 메시지
• 에러 메시지 읽기// T의 크기가 4바이트가 아니면 컴파일 에러가
발생하도록 하였음(C++11)
void Push(const T& item) {
static_assert( sizeof(T) == 4, "The size of T should be 4 bytes." );
}
1>h:\home\noel\documents\personal\dev\work\temp\tutorial1\tutorial1.cpp(149): error C2338: The size of T should be 4
bytes.
1>
h:\home\noel\documents\personal\dev\work\temp\tutorial1\tutorial1.cpp(149) : while compiling class template member
function 'void MyStack<T>::Push(const T &)'
1>
with
1>
[
1>
T=double
1>
]
1>
h:\home\noel\documents\personal\dev\work\temp\tutorial1\tutorial1.cpp(162) : see reference to function template
instantiation 'void MyStack<T>::Push(const T &)' being compiled
1>
with
1>
[
1>
T=double
1>
]
1>
h:\home\noel\documents\personal\dev\work\temp\tutorial1\tutorial1.cpp(158) : see reference to class template
instantiation 'MyStack<T>' being compiled
1>
with
1>
[
1>
T=double
1>
]
63
STL(Standard Template Library)
• 표준 템플릿 라이브러리에는 중요한 자료 구조와
알고리즘들이 미리 구현되어 있음
(Generic한 라이브러리의
• 검증된 라이브러리 - 개발 기간을 단축할 수 있음
특징상 성능을 극한으로
끌어올리진 않았음)
• 각 자료 구조들의 특성을 잘 이해해야 함
• 중요 개념
– 컨테이너(Container) : 순차 컨테이너, 연관 컨테이너
– 반복자(Iterator) : 컨테이너의 원소들을 탐색하기 위함
– 알고리즘(Algorithms) : 기본적인 알고리즘들이 정의되어 있음
64
STL 컨테이너(Container)
• Sequence Container
• Vector
동적 배열
• List
순서가 있는 리스트
• Deque
양 끝에서 입력과 출력 가능
• String
문자열
• Associative Container
• Map
사전과 같은 구조 <key, value>
• Multi Map 중복 key를 허용
• Set
집합 <key>
• Multi Set 중복 key를 허용
• Adaptor Container
• Stack
• Queue
• Priority Queue
65
std::string
• ‘문자열’을 처리하기 위한 클래스로 C의
char*을 직접 다루는 것에 비해 매우 편리
string
string
string
string
cout
cout
cout
cout
#include <string>
message = "hello world";
message2( "c++" );
message3( 10, '=' );
message4 = message;
<<
<<
<<
<<
message << endl;
message2 << endl;
message3 << endl;
message4 << endl;
* C의 null-terminated string과 다른 점  C에서는 문자열의 끝을 나타내기
위해 마지막에 ‘\0’을 통해 나타냈으나 std::string에서는 size를 담고 있는
변수가 있으므로 ‘\0’과 같은 null 문자가 필요 없다.
66
std::string [2]
문자열 길이(strlen)
cout << message.size() << endl; // 11
string::operator+(const string&)에 의한 문자열 결합(strcat)
string name;
cin >> name;
string msg = "Hello world, " + name + "!";
cout << msg << endl;
첨자 접근
msg[0] = 'y';
문자열간 비교
(name == "quit")
그 외에도 문자열 검색을 위한 find 멤버 함수,
부분 문자열 추출을 위한 substr 등…
C의 문자열 함수와 씨름하던 것에 비하면…
67
std::string [3]
• typedef basic_string<char, char_traits<char>,
allocator<char> > string;
• typedef basic_string<wchar_t, char_traits<wchar_t>,
allocator<wchar_t> > wstring;
• 1byte 단위의 string과 2byte 단위의 wstring이 있음
• 두 string이 완전히 다른 것이 아니라 글자 하나에 대한 type이 char인지
wchar_t 인지에 대한 차이밖에 없음
• traits 라는 개념을 통해 문자열 비교, 길이 구하기, 검색 등의 기능을
재정의 할 수 있음
• 예를 들어 string과 같이 char 기반의 문자열이지만 대소문자를
구별하지 않는 문자열 istring 등을 traits 재정의를 통해 만들어낼 수
있음  Generic Programming의 매력
68
std::vector<T>
• ‘동적 배열’
#include <vector>
– 저장할 데이터 개수가 가변적일 때
– 중간에 데이터 삽입/ 삭제가 없을 때(한 칸씩 당기거나 밀기
때문에 속도 저하)
– 빈번하게 데이터를 검색하지 않을 경우
– 데이터 접근을 랜덤하게 하고 싶을 때(첨자를 통한 접근)
[0]
[1]
[2]
[3]
[4]
[5]
sizeof(T)
vector<int> scores(5);
for (int i = 0; i < scores.size(); ++i)
cin >> scores[i];
69
std::vector<T> [2]
Ex) 문자열(std::string) 벡터
vector<string> animals;
animals.push_back("dog");
animals.push_back("cat");
for (int i = 0; i < animals.size(); ++i)
cout << animals[i] << endl;
Member functions
기능
push_back(val)
컨테이너의 마지막에 원소를 추가한다. 미리 만들어
둔 버퍼가 가득차면 추가로 확장한다.
pop_back
마지막에 원소 삭제
insert
특정 위치에 원소 삽입
erase
특정 위치의 원소 삭제 또는 지정 범위 원소 삭제
clear
초기화(모든 원소 삭제)
back
제일 마지막 원소 반환
70
std::deque<T>
#include <deque>
• 벡터에서 push_back(val), pop_back()에
앞에서도 추가/삭제 : push_front(val), pop_front()
• queue<T>, stack<T>는 내부적으로 deque<T>를
사용하도록 되어있음
71
std::map<key_type, T>
• ‘사전’ 형식의 자료 구조
• data[“key”] : 검색 뿐만 아니라 value_type의 초기값이
들어가므로 단순 검색에는 find 함수를 이용해야 함
string tmp;
map< string, int > count;
while (true){
cin >> tmp;
if (tmp == "q") break;
count[tmp]++;
}
a friend in need is a friend indeed
a:2
friend : 2
in : 1
indeed : 1
is : 1
need : 1
for (map<string, int>::const_iterator itr =
count.begin(); itr != count.end(); ++itr){
cout << itr->first << " : " << itr>second << endl;
}
cout << count[“smith”] << endl;  없는 단어이므로 0이 나오겠지만
<“smith”, 0>이라는 항목으로 tree에 추가가 된다.
72
std::map<key_type, T>
• 내부 구현은 Red Black Tree. 임의의 키에 대해서 O(log
N)의 접근
• Find 함수로 검색
map< string, int > score;
score["dog"] = 96;
score["cat"] = 90;
string name;
cin >> name;
map< string, int >::const_iterator itr = score.find(name);
if (itr != score.end()) {
cout << itr->second;
}
else{
cout << "not found" << endl;
}
73
Container의 올바른 선택
Container
Random
Access
Insert
Erase
Find
Sort
list
N/A
C
C
N
N log N
vector
(deque)
C
C~N
C~N
N
N log N
set
C
log N
log N
log N
C
map
log N
log N
log N
log N
C
• 중복된 key를 허용하고 싶을 때 multiset, multimap
• 모든 경우에 대해 최적의 컨테이너는 없고 상황에 따라 효율적인 컨테이너를
골라 써야 한다.
74
반복자(Iterator)
• 컨테이너의 순회 방법을 일반화하기 위해 만든 개념
–
–
–
–
컨테이너 요소 하나를 가리킬 수 있다
가리키는 지점의 요소를 읽거나 쓸 수 있다. * 연산자.
증감 연산자에 의해(++, --) 다음이나 이전 위치로 이동할 수 있다.
반복자끼리 대입, 비교 연산이 가능하다.
• 벡터의 경우 random access가 가능하므로 index를
증가시키는 for 문을 사용해도 무방
• 그러나 다른 종류의 컨테이너에 대해서는?
– 반복자를 사용하면 컨테이너 순회를 정말 간결하게 표현할 수
있음
75
반복자(Iterator)
• 컨테이너를 순회하면서 모든 원소 출력
typedef std::vector<int> Container;
Container intArray;
intArray.push_back(1);
intArray.push_back(2);
intArray.push_back(3);
• 컨테이너의 종류가 다른 것(list,
deque)으로 바뀌어도 아래 코드는
변하지 않는다
for (Container::iterator itr = intArray.begin(); itr != intArray.end(); itr++)
{
cout << *itr << endl;
}
• 검색이나 단순 순회처럼 컨테이너에 들어있는 원소 값을 바꾸지 않는다면
상수 반복자를 사용할 수 있다. Iterator 대신 const_iterator를 쓴다.
begin -> cbegin, end -> cend
76
Operator Overloading
표현식
멤버 함수
멤버 함수가 아닐 때
예제
@a
a.operator@()
operator@(a)
!std::cin  std::cin.operator!()
a@b
a.operator@(b)
operator@(a.b)
std::cout << “hello world” 
std::cout.operator<<(const std::string&)
a=b
a.operator=(b)
X
std::string s; s = “abc”;
std::string.operator=(const char*)
a[b]
a.operator[](b)
X
std::map<int, int> m; m[1] = 2;
 m.operator[](int)
a->
a.operator->(b)
X
std::unique_ptr<…> ptr; ptr->foo(); 
ptr.operator->()
a@
a.operator@(0)
operator@(a,0)
std::vector<…>::iterator i; i++; 
i.operator++(0);
77
Operator + Overloading
class ComplexNumber{
public:
ComplexNumber operator+(const ComplexNumber& e){
return ComplexNumber(this->real + e.real, this>imaginary + e.imaginary);
}
• 복소수 끼리 더하는 연산
• A @ B 형태이고, 멤버 함수에서 operator@(B) 형태로 overload 가능
78
Operator << Overloading
class ComplexNumber{
public:
ComplexNumber(double real_, double imaginary_)
: real(real_), imaginary(imaginary_) {}
friend ostream& operator<< (ostream& os, ComplexNumber& a){
os << a.real << "+" << a.imaginary << "i";
return os;
}
/* 생략 */
private:
double real;
double imaginary;
};
ComplexNumber c1(1, 2);
cout << c1 << endl; // 1+2i
* 의의 : stream만 잘 정의되면 화면
출력이든, 파일이든, 아니면 네트워크든
모두 같은 양식으로 보낼 수 있다.
friend 를 쓴 이유는 클래스의 멤버는 아니지만 클래스의 보호된 멤버에
접근하기 위해서임(특수한 경우에만 사용해야함)
79
Operator() Overloading
• Function Object 구현 시 사용(Functor라고도 불림)
• STL의 많은 부분에서 사용되고 있음
// TEMPLATE FUNCTION sort
template<class _RanIt> inline
void sort(_RanIt _First, _RanIt _Last)
{
// order [_First, _Last), using operator<
_STD sort(_First, _Last, less<>());
}
// TEMPLATE STRUCT less
template<class _Ty = void>
struct less
: public binary_function<_Ty, _Ty, bool>
{
// functor for operator<
bool operator()(const _Ty& _Left, const _Ty& _Right) const
{
// apply operator< to operands
return (_Left < _Right);
}
};
80
알고리즘(Algorithm)
• for_each, find, find_if, count, count_if, …
• copy, swap, transform, replace, fill, generate, remove,
unique, reverse, rotate, shuffle, …
• partition, sort, nth_element, …
• binary_search, …
• min, max, …
• next_permutation, prev_permutation, …
• http://www.cplusplus.com/reference/algorithm/
81
std::for_each
• std::for_each(시작 iterator, 끝 iterator, 함수);
struct PrintIntElement{ void operator()(int& elem){
cout << elem << endl;
} };
std::for_each(intArray.begin(), intArray.end(), PrintIntElement());
<Function object의 활용>
std::for_each(intArray.begin(), intArray.end(), [](int& elem){
cout << elem << endl; } );
<C++11에서 더 간결해진 문법>
Algorithm 함수를 사용하여 코드를 만든 의도가 더 잘 드러나도록 할 수
있다.
82
std::sort
vector<int> data;
data.push_back(2);
data.push_back(3);
data.push_back(1);
sort(data.begin(), data.end()); // 오름차순 정렬
struct Desc{
bool operator()(const int& e1, const int& e2){
return e1 > e2;
}
};
sort(data.begin(), data.end(), Desc()); // 내림차순 정렬
Predicate 함수를 갈아끼우는 방식으로 여러 가지 조건 구현 가능
83
Algorithm 기타 예제
transform 대문자로의 변환
string msg = "The STL algorithms are generic because ...";
transform(msg.begin(), msg.end(), msg.begin(), toupper);
next_permutation 순열 생성
vector<int> a(3);
for (size_t i = 0; i < a.size(); ++i)
a[i] = i + 1;
do
{
copy(a.begin(), a.end(), ostream_iterator<int>(cout, "\t"));
cout << endl;
1
2
3
} while (next_permutation(a.begin(), a.end()));
1
3
2
2
1
3
2
3
1
3
1
2
3
2
1
Press any key to continue . . .
84
예외(Exception) 처리
• C에서의 에러 처리 방법
– 함수의 리턴값으로 판단하기
(unix 함수들은 에러가 발생하면 대부분 -1 리턴)
– global 변수에 저장된 에러 번호
• (윈도우) GetLastError() 함수
• (unix) errno() 함수
– 개중에는 프로그램을 더이상 진행시킬 수 없는 에러도 있음
• C++에서는 예외(exception) 처리 기능을 제공
– 프로그램 실행 도중에 비정상적인 상황이 발생하여 더 이상
진행할 수 없을 때, 예외를 발생시킴
85
try-catch 구문
try{
int *buffer = 0; //new int[N];
if (buffer == nullptr)
throw exception("allocation failed");
int *buffer_2 = 0; //new int[N];
if (buffer_2 == nullptr)
throw bad_exception();
throw 123;
}
catch (bad_exception& e){
cerr << e.what() << endl;
}
catch (exception& e){
cerr << e.what() << endl;
}
catch (int errorNumber){
cerr << errorNumber << endl;
}
catch (...){
cerr << "unknown exception" << endl;
}
• try 블록 안의 명령들을
실행하다 throw 문에서
예외가 발생하면 catch로
넘어가 예외를 처리한다.
• catch(…)은 catch
구문에서 받기로
지정하지 않은 type의
예외가 들어왔을 때의
처리다.
• C++에서 정의된 예외만
핸들링 가능
• 만약 catch해주는 코드가
없으면 함수를 바로
빠져나가 그 상위
함수에서 찾는다.
• Main까지 가도 없으면
프로그램이 종료됨
86
RTTI (Runtime Type Information)
• 프로그램 실행 중 객체의 type을 알 수 있도록 함
• dynamic_cast, typeid 연산자, type_info 클래스
class Super { public: virtual ~Super(){} };
class Base : public Super {};
class Derived : public Base {};
Super* p = &derived;
cout << typeid(*p).name() << endl;
Super? or Derived?
Super super;
Base base;
Derived derived;
cout
cout
cout
cout
cout
<<
<<
<<
<<
<<
typeid(super).name() << endl;
typeid(base).name() << endl;
typeid(derived).name() << endl;
typeid(int).name() << endl;
typeid(int*).name() << endl;
class Super
class Base
class Derived
int
int *
Press any key to continue . . .
87
형변환(Casting)
• Implicit casting (암시적 캐스팅)
• Explicit casting (명시적 캐스팅)
int i = 10;
char c = i; // implicit cast
char d = (char)i; // explicit cast (C style)
char e = char(i); // explicit cast (C++ style)
• C++의 세분화된 casting
–
–
–
–
static_cast
dynamic_cast
reinterpret_cast
const_cast
88
C에서의 casting의 관대함
32bit에서는 int와 int*의 크기가 같으므로 아무런 문제가 없었지만
64bit에서 int*의 크기는 8bytes, int의 크기는 그대로 4bytes이므로
aabbccdd 만 출력됨
89
static_cast
• 실수형—정수형, 정수형—열거형(enum) 등의 기본 데이터
타입 간의 변환
• void pointer를 다른 형식의 pointer로 변환
• 서로 다른 type간의 pointer끼리는 변환 불가
A* -> void* -> B* 는 됨…
90
dynamic_cast
• 모호한 포인터에 대한 변환
• RTTI(Runtime Type Information) 정보를 확인하므로
– 추가적인 오버헤드.
• 실패 시 null 을 리턴
Rectangle* -> Shape*나
Circle* -> Shape*는 문제가 없음
Shape
그렇다면 Shape*를 Rectangle*로 변환할
수 있는지? (down-casting)
Rectangle
Circle
91
reinterpret_cast
•
•
포인터끼리 아무런 제약없이 변환
정수형 <-> 포인터
const_cast
•
포인터의 상수성을 제거하는 데 사용
int data = 301;
int* ptr = &data;
double PI = 3.14;
const double* ptr = &PI;
cout << *ptr << endl;
//cout << *static_cast<double*>(ptr) << endl;
cout << *reinterpret_cast<double*>(ptr)
<< endl;
// *ptr = 3.1415;
// expression must be a modifiable
value
301
-9.25596e+061
Press any key to continue . . .
*const_cast<double*>(ptr) = 3.1415;
cout << *ptr << endl;
92
RAII(Resource Acquisition Is Initialization)
• Scope Bound Resource Management
• 객체가 생성될 때 리소스를 획득, 객체가 소멸할 때
리소스를 반환
try{
FILE *fp;
fopen_s(&fp, "input.txt", "r");
/* some works */
throw exception();
fclose(fp); // 안 불림!!!
}
catch (exception& e){
cerr << e.what() << endl;
}
어떤 원인에서든지(throw, return,
break, goto 등등) 리소스를 획득하는
부분과 리소스를 반환하는 부분이
짝이 되지 못하게 되면 프로그램이
종료될 때까지 해당 리소스를 사용한
채로 열어두게 된다.
93
RAII(Resource Acquisition Is Initialization)
class _ifstream{
public: _ifstream(const char*) { cout << "open!" << endl; }
virtual ~_ifstream(){ cout << "close!" << endl; }
};
실제 ifstream의 동작을 보기 어려우므로(…)
try{
_ifstream ifs("input.txt");
/* some works */
throw exception();
open!
close!
Unknown exception
Press any key to continue . . .
}
catch (exception& e){
cerr << e.what() << endl;
}
Scope에서 벗어나는 순간 무조건 소멸자가 불리므로 앞 예제에서
하지 못했던 리소스 반환 절차를 진행할 수 있다.
94
Smart Pointer
• new 와 delete의 짝을 맞춰주는 일도 항상 골칫거리
• Smart Pointer (Reference Counted Pointer)
– 포인터 객체가 생성될 때 +1, 소멸될 때 -1
– 소멸자에서 Count값이 0이 되면 delete한다.
– 포인터끼리 복사가 일어나면 +1
{
shared_ptr<_ifstream> p(new _ifstream("input.txt")); // ref = 1
{
ConfigLoader loader(p); // ref = 2
} // ref = 1
} // ref = 0 -> delete
• Overhead : 카운터를 저장할 공간(4bytes?)
95
Smart Pointer
• Reference Counted가 잘못 동작하는 경우
• Type A인 객체가 Smart Pointer<Type B>를 들고 있고
• Type B인 객체가 Smart Pointer<Type A>를 들고 있을 때
• 즉, cyclic 관계가 형성될 때 Reference Count가 제대로 되지 않는다.
Shared_ptr<A>
ref = 1
shared_ptr<B>
ref = 1
B가 생성되면서 A를 참조
Shared_ptr<A>
ref = 2
shared_ptr<B>
ref = 1
A가 소멸되나
ref=2에서 ref=1이므로
delete a;는 호출되지 않는다
Memory leak 발생
B가 먼저 소멸
Shared_ptr<A>
ref = 2
shared_ptr<B>
ref = 0
delete b;
96
소스 코드 버전 관리
• 소스 코드의 변경 사항을 추적하고 여러 사람이 협업하여
소프트웨어를 만들기 위한 도구
– 코드를 여기저기 대량으로 수정했는데 잘못된 수정!
(Diff & Revert)
– 여러 사람이 각 파트를 맡아 개발한 뒤 자동으로 합쳐주는 건
없을까?
(Merge)
– 실험적인 기능을 넣고 싶은데 기존 개발 흐름에 방해가 될 것
같음
(Branch)
• 현재 널리 쓰이는 버전 관리
– SVN (Subversion)
– Git
서버-클라이언트 구조
분산 저장소 시스템(서버가 필요 없음)
97
SVN 구성
• SVN 서버
– http://www.visualsvn.com/ 에서 무료로 다운로드 가능
• SVN 클라이언트
– http://tortoisesvn.net/ - 탐색기와 연동됨
• Visual Studio 플러그인
– AnkhSVN https://ankhsvn.open.collab.net/
98
Git 구성
• 로컬에서만 작업하면 Git 서버 필요 없음
• 무료 원격 저장소(개인용)
– Bitbucket https://bitbucket.org/
• Git 클라이언트
– SourceTree http://www.sourcetreeapp.com/
– TortoiseGit https://code.google.com/p/tortoisegit/
• 보다 친절한 설명서(Googling: git 설명서)
– http://rogerdudler.github.io/git-guide/index.ko.html
– http://git-scm.com/book/ko/
99
Diff in SourceTree
이전에 commit했던 버전과 비교하여 뭐가 달라졌는지 볼 수 있다.
100
Revert 기능
잘못된 수정인 경우 특정 시점으로 돌아갈 수도 있고 변경 이력에 누적
101
Debugging 기능 잘 활용하기
• IDE(Visual Studio, Eclipse, …)에서 제공하는 디버깅 기능을 잘
이용하기
• 디버거 기본 기능
– Breakpoint
• 실행을 멈추고 싶은 지점에서 일시 정지
– Step by step
• 한줄씩 실행하기(Step over)
• 현재 실행하려는 함수 안으로 들어가기(Step in)
• 현재 함수를 실행하고 바깥으로 나오기(Step out)
– Watch
• 각종 변수 값을 확인할 수 있음
– Call Stack
• 이 함수로 어떻게 들어왔고 어떤 상황인지 파악
– Memory & Register
102
Visual Studio Debug메뉴
103
Debugging 예제 – is_prime
bool is_prime(int n){
int cnt = 0;
for (int i = 1; i*i < n; ++i){
if (n%i == 0){
cnt++;
if (cnt>1) return false;
}
}
if (cnt == 1) return true;
return false;
}
int _tmain(int argc, _TCHAR* argv[])
{
for (int i = 1; i < 100; ++i){
if (is_prime(i))
cout << i << "\t";
}
cout << endl;
return 0;
}
2
3
4
5
7
9
11
13
17
19
23
25
29
31
37
41
43
47
49
53
59
61
67
71
73
79
83
89
97
Press any key to continue . . .
처음부터 Step by Step 으로
들어가면 너무 오래 걸리니까
Is_prime 함수의 첫번째 줄에
Conditional Break를 건다.
즉, n = 9일 때 break를 걸어보자
104
Breakpoints Window
105
Breakpoint Condition
우선 원하는 줄 왼쪽 칸을 클릭 해
빨간 원 모양의 break point를 설정한 뒤,
Condition… 을 클릭
n == 9 일 때 멈춘다
Conditional Breakpoint는 십자 표시
106
When Breakpoint Is Hit
9의 약수는 1,3,9이므로 3까지는 계산했어야 했는데
2까지밖에 계산 안함
107
Local Variables & Watch
현재 scope 상에서 살아있는 모든 local 변수의
type과 값을 알려준다.
Locals에 자동으로 뜨는 변수
말고 다른 변수 값을 보고
싶으면 Watch 창을 이용한다.
주소를 얻어오거나 포인터
참조 등을 하는 간단한 연산
정도는 됨
108
Call Stack
현재 Break가 걸린 지점을 기준으로 누가 이 함수를 불렀는지
알아낼 수 있다.
109
Call Stack (@ Stack Overflow)
110
Call Stack (Symbol Loading)
• 디버그 모드로 빌드되거나 디버그 심볼이 공개된
프로그램(또는 모듈)에 한해서 Symbol 파일을 로드하면
기계어 주소 대신 함수명을 볼 수 있고 추가적인 디버그
정보를 얻을 수 있다.
회색으로 표시된 글자 ntdll.dll!770da22b() 이 부분이 심볼 파일이
로드 되지 않은 것이고, 아래 스택 프레임은 정확하지 않을 수도 있음
111
Call Stack (Symbol Loading)
옵션  Debugging  Symbols 에서 Microsoft Symbol Servers를 지정하면
MS에서 제공하는 심볼 파일(*.pdb)을 얻을 수 있다. 심볼 파일이 로드되면
아까전 기계어 주소로 표시되었던 함수가 원래 함수 이름으로 바뀐다.
112
Magic Debugging Number
ABABABAB
HeapAlloc으로 메모리 할당 후 가드 바이트에 채워진 값
CCCCCCCC 초기화 되지 않은 스택 메모리
CDCDCDCD 메모리 할당 후 초기화 되지 않은 힙 메모리
BAADF00D
LocalAlloc(LMEM_FIXED)으로 메모리 할당된 후 초기화 되지 않은 값
FDFDFDFD
할당된 메모리의 전후 가드용 바이트에 채워지는 값
FEEEFEEE
힙 메모리를 해제한 후 채워지는 값
int uninitialized_stack;
int *pa = &uninitialized_stack;
cout << hex << *pa << endl; // ccccccc
int *pb = new int[16];
cout << hex << pb[0] << endl; // cdcdcdcd
Visual Studio 기준 & Debug 모드일 때만
113
대형 C++ 프로젝트에서는 빌드 시간이
문제
• 코드 한줄 수정하고
– 빌드 1시간
• 완화 방법
–
–
–
–
–
불필요한 Include는 항상 제거하는 습관
전방 선언(Forward Declaration) 활용
미리 컴파일된 헤더(Precompiled Header)
Pimpl idiom
Cpp 묶어서 빌드하기(unity build)
• 실제 프로젝트의 복잡도가 높아서라기 보단 잘못 설계된
헤더 파일 관계때문인 경우가 허다…
114
C++ Include Tree
정말 간단한 프로그램을
빌드하기 위해서도
엄청나게 많은 헤더
파일들이 필요함
결국 cpp 파일마다 빌드가
되어야 하는데 참조하지
않아도 될 include
덩어리들에 의한 부하가
상당해진다
사실 왼쪽 그림은 매우
양호한 편
115
Show Includes 옵션으로 검사하기
116
Output Window 출력 메시지(앞부분)
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
1>
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
Note:
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
including
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
file:
r:\debugging\stdafx.h
r:\debugging\targetver.h
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\SDKDDKVer.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\stdio.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\crtdefs.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\sal.h
c:\program files (x86)\microsoft visual studio 10.0\vc\include\codeanalysis\sourceannotations.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\vadefs.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\swprintf.inl
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\tchar.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\crtdefs.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\wchar.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\crtdefs.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\wtime.inl
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\iostream
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\istream
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\ostream
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\ios
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\xlocnum
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\climits
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\yvals.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\crtdefs.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\use_ansi.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\limits.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\crtdefs.h
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\cmath
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\math.h
117
전방 선언(Forward Declaration)
• ClassA.h, ClassA.cpp, ClassB.h, ClassB.cpp 가 있을 때,
• ClassB 작성 시 ClassA가 필요하다면?
– #include “ClassA.h”을 ClassB.h 파일에 넣기
– 이제 ClassA.h가 수정되면 ClassB.h 도 바뀌었고, ClassB.cpp도 바뀌므로
다시 빌드 되어야 함(여기까진 문제가 없음)
– 그러나 ClassA.h와 전혀 관계없던 ClassC.h에서 ClassB.h를 참조하고
있었다. (흔한 상황)
– 이렇게 꼬리에 꼬리를 물다보면 하나 수정해도 전체 프로젝트가 빌드됨
(-_-)
• 전방선언을 활용
– ClassB.h 위에 class ClassA; 라고만 해둔다. 이후 ClassB.cpp에서
#include “ClassA.h”를 해주고
– 단, ClassA의 포인터에 대해서만 가능한 방법. 즉, Class B 내에
필연적으로 ClassA a 와 같이 일반 변수가 들어가야 한다면 이 방법을
사용할 수 없다. 그러나 대부분 기능 자체가 필요해서 쓰는 경우가
많으므로 ClassA *a로 두고 heap에 생성하는 식으로 꼼수를 쓸 수 있다.118
Precompiled Header
• C/C++ 빌드 과정에서 매번 반복적으로 컴파일하는 엄청난 분량의
헤더 파일 부하를 줄이기 위함
• stdafx.h의 정체(VS기준; linux gcc에서는 별도로 설정)
• 그렇다고 아무거나 넣으면 안됨(잘 변경되지 않는 라이브러리
위주로)
119