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