Basic

OOP 기본 원칙
- 추상화(Abstraction)
클래스는 개체의 특성과 멤버함수를 정의한다. 클래스는 특성의 불변성을 정의할 수 있다.
(인터페이스로 클래스의 공통적인 특성; 변수, 메소드를 표현)
- 캡슐화(Encapsulation)
구현의 세부 사항을 숨기는 원칙(변수와 메소드를 하나로 묶음)
내부 특성은 불변성을 위반하지 않기 위해서 직접 접근할 수 없으며, 클래스의 멤버 함수를 통해서만 접근
(public 멤버 변수는 내부 특성이 아닌, 클래스 인터페이스의 일부이다.)
- 상속(Inheritance)
파생 클래스가 기본 클래스의 모든 변수와 멤버 함수를 포함한다.
(캡슐화를 유지하고, 클래스의 재사용성이 증가)
- 다형성(Polymorphism)
문맥이나 매개변수에 따라 해석하는 식별자의 능력
Overloading & Overriding & template 특수화(인스턴스화) 등
class person{
public:
person(){}
explicit person(const std::string& name): name(name) {}
void set_name(const std::string& n){ name=n; }
std::string get_name(void) const { return name; }
void all_info(void) const {
std::cout<<"[person] My name is "<<name<<std::endl;
}
private:
std::string name;
};
일반적인 OOP 클래스에는 멤버 변수를 위한 게터(getter) 멤버 함수와 세터(setter) 멤버 함수를 포함한다.
(이는 캡슐화의 개념과 모순되기 때문에 무조건적으로 사용하는 것은 좋지 않음)
class student : public person{
public:
student(const std::string& name, const std::string& passed): person(name), passed(passed) {}
// Polymorphism()
void all_info(void) const {
std::cout<<"[student] My name is "<<get_name()<<std::endl;
std::cout<<"I passed the follwing grades: "<<passed<<std::endl;
}
private:
std::string passed;
};
person을 파생한 student 클래스는 person의 모든 멤버 변수와 멤버 함수를 포함한다.

파생클래스의 멤버 변수나 함수의 가시성은 내부 스코프와 비슷하다.
클래스의 멤버 외에도 기본 클래스(파생받은 클래스)를 볼 수 있다.
파생 클래스에 동일한 이름을 갖는 멤버 변수나 함수가 있다면, 기본 클래스의 멤버 변수나 함수가 숨겨진다.
하지만, person::all_info와 같이 이름(namespace)를 한정해주면, 숨겨진 변수나 함수에 접근 가능하다.
person mark("Mark Markson");
mark.all_info();
student tom("Tom Tomson", "Algebra, Analysis");
tom.all_info();
person p(&tom);
person& pr=tom; // pr(tom) or pr{tom}
person* pp=&tom; // pp(&tom) or pp{&tom}
p.all_info();
pr.all_info();
pp->all_info();
- student를 person에 복사
- student를 person이라고 지칭
- student를 함수에 person 인수로 전달
위의 새가지 경우 모두 프로그램은 컴파일되고 실행되지만, 추가 학생 정보는 출력되지 않는다.

파생클래스는 기본 클래스의 서브타입(Sub-type)이며,
기본 클래스(base class)가 필요한 모든 곳에서 파생 클래스(derived class)를 허용한다.

업 캐스팅(up-casting): 기본 클래스의 포인터로 파생 클래스의 객체를 가르킴(문제 X)
다운 캐스팅(down-casting): 기본 클래스 포인터가 가리키는 객체를 파생 클래스의 포인터로 가르킴(문제 O)
파생 클래스의 포인터는 기본/파생 클래스의 멤버에 접근이 가능하지만,
기본 클래스의 포인터로는 기본 클래스의 멤버에만 접근이 가능하다.
    객체 생성 순서
1. 메모리 할당
2. Base Class 생성자 실행
3. Derived Class 생성자 실행
    객체 소멸자 순서
1. Derived Class 소멸자 실행
2. Base Class 소멸자 실행
기본 클래스에서 파생할 때, 파생 클래스에서 상속된 멤버의 접근을 얼마나 제한할지 지정할 수 있다.
클래스를 protected로 파생할 경우, public인 기본 클래스의 멤버는 protected가 되고 다른 멤버는 접근성을
유지한다.
private로 파생된 클래스의 멤버는 모두 private이 된다.

default로 class로 정의할 경우 private으로 상속을 하고, struct로 정의할 경우 public으로 상속한다.
생성자 상속
기본 클래스에서 생성자는 암시적으로 상속되지 않는다.
class person{
public:
explicit person(const std::string& name): name(name) {}
// ...
};
class student: public person{}; // string
int main(void){
student tom("Tom Tomson"); // error
}
C++11에서 using을 사용하면, 생성자를 상속받을 수 있다.
class student: public person{
using person::person;
};
만일 두 클래스에 동일한 서명을 갖는 생성자가 있을 경우,
파생 클래스의 생성자가 우선된다.
가상 함수와 다형성
가상(virtual) 함수의 존재는 클래스의 행동을 근본적으로 변화시킨다.

하나 이상의 가상 함수를 포함하는 클래스를 다형성 타입(Polymorphic Type)이라고 한다.
가상함수는 기본 클래스 내에서 상속되어 파생 클래스에서 재정의되는 멤버함수이다.
포인터(Pointer) 또는 참조(Reference)를 사용하여 파생 클래스의 객체를 참조하면 해당 객체에 대해
가상 함수를 호출하고, 파생 클래스의 함수를 실행할 수 있다.

실행 시간(Runtime)에 함수의 다형성(Polymorphism)을 구현하는데 사용된다.
class person{
virtual void all_info(void) const { cout<<"My name is "<<name<<endl; }
virtual ~person() {}
// ...
};
class student: person{
virtual void all_info(void) const {
person::all_info(void) const {
person::all_info();
cout<<"I passed the follwing grades: "<<passed<<endl;
}
}
// ...
};
컴파일러 동작
1. pr 또는 pp의 정적 타입은 무엇인가? 즉, pr 또는 pp는 어떻게 선언되어 있는가?
2. 해당 클래스에 all_info라는 이름의 함수가 있는가?
3. all_info함수에 접근할 수 있는가? 아니면, private 함수 인가?
4. 가상 함수인가? 그렇지 않다면 함수 호출
5. pr 또는 pp의 동적 타입은 무엇인가? 즉, pr 또는 pp가 참조하는 개체의 타입은 무엇인가?
6. 해당 동적 타입의 all_info 함수를 호출
person mark("Mark Markson");
mark.all_info();
student tom("Tom Tomson", "Algebra, Analysis");
tom.all_info();
person p(&tom);
p.all_info();
person& pr=tom; // student
pr.all_info();
person* pp=&tom; // student
pp->all_info();
컴파일러는 위와 같은 동적 함수 호출을 구현하기 위해 가상 함수 테이블(Virtual Funciton Table);
가상 멤버 함수 테이블(Virtual Method Table)또는 Vtable을 생성한다.

가상 함수 테이블은 실제 개체의 각 가상 함수를 호출하는 함수 포인터를 포함한다.
(함수 포인터의 간접 참조로 인해 가상 함수에 약간에 비용이 추가됨)

런타임에 실행할 멤버 함수를 선택하는 메커니즘을 지연 바인딩(Late Binding) 또는
동적 바인딩(Dynamic Binding) 이라고 한다.
지연 바인딩이나 동적 바인딩은 템플릿을 사용한 정적 다형성과 달리 동적 다형성(Dynamic Polymorphism)을
나타낸다.
Effective C++] 7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자. :: 프로그래머 싸이
void spy_on(const person& p){
p.all_info();
}
위의 함수는 지연 바인딩 때문에 기본 클래스(person)을 가리키는 레퍼런스를 전달할 때에도
파생 클래스의 가상 함수를 제공할 수 있다.

지연 바인딩은 호출되는 클래스의 인스턴스 수와 관계 없이 코드가 실행 파일에 오직 하나만 존재한다는 점이다.
void glueless(person p){
p.all_info();
}
값에 의한 전달로 함수에 인수를 전달하면, 지연 바인딩을 사용할 수 없다.
(파생 클래스에서 가상 함수 호출을 방해한다.) : 슬라이싱(Slicing)

다형성 타입은 항상 레퍼런스나 (스마트) 포인터로 전달해야 한다.
명시적 오버라이딩
오버라이딩된 멤버 함수가 다른 서명을 갖는 경우
class person{
virtual void all_info(void) const { /* ... */ }
};
class student: public person{
virtual void all_info(void){ /* ... */ }
};
int main(void){
student tom("Tom Tomson, "Algebra, Analysis");
person& pr=tom;
pr.all_info();
}
위에서 person::all_info와 student::all_info는 서로 다른 서명을 가지기 때문에,
person::all_info는 pr.all_info()에 구속되지 않는다.

C++11에서 override 키워드가 추가되었다.
class student: public person{
virtual void all_info(void) override { /* ... */ }
};
override라고 선언함은, 기본 클래스의 virtual 함수를 오버라이드함을 의미한다.
만약 조건을 만족하는 함수가 없다면 컴파일러은 경고를 출력한다.

virtual이 아닌 함수에 override를 사용하면, 오류 발생
C++11에서 final이라는 키워드도 추가되었다.
fianl은 가상 멤버함수를 오버라이드 할 수 없다고 선언한다.
컴파일러는 final 키워드를 통해 가상 테이블로 간접 호출하는 멤버 함수를 직접 호출하도록
바꿀 수 있다.

클래스 전체에 final 선언시, 해당 클래스로부터 파생되는 경우를 막을 수 있다.

override는 슈퍼 클래스와 관련있지만, final은 서브 클래스와 관련이 있는 특성이다.
추상 클래스
순수 가상 함수와 추상 클래스
=0으로 선언하는 virtual 함수를 순수 가상 함수(Pure Virtual Function)라고 한다.
순수 가상 함수를 포함하는 클래스를 추상 클래스(Abstract Class)라고 한다.
class creature{
virtual void all_info(void) const=0; //
};
class person: public creature{
// ...
};
int main(void){
creature som_beast; // :
person mark("Mark Markson");
mark.all_info();
return 0;
}
추상 클래스를 이용해서 인스턴스를 생성할 수 없다.(레퍼런스와 포인터로는 선언 가능)
추상 클래스를 상속한 클래스는 순수 가상 함수를 포함하지 않도록 순수 가상 함수를 오버라이드 해야 한다.
(서버 클래스의 개체는 모든 순수 가상 함수가 오버라이딩 될 때만 빌드(build) 할 수 있다.)

추상 클래스를 Java의 인터페이스라고 볼 수 있다.

Java에서 모든 멤버 함수는 본질적으로 virtual이다.
Java에서는 Interface 기능을 제공하는데, 멤버함수는 오직 선언만 가능하고, 정의할 수 없다.
(모든 멤버 함수가 순수 가상 함수인 C++ 클래스가 Java의 Interface라고 할 수 있음)
- 인터페이스: 구현 없음
- 추상 클래스: 기본 구현
- 특정 클래스
상속을 통한 펑터
상속으로도 펑터(functor)를 구현할 수 있다.
// interface
struct functor_base{
virtual double operator()(double x) const=0;
};
위의 공통 기본 클래스는 인터페이스로만 사용되기 때문에 추상적이다.
double finite_difference(functor_base const& f, double x, double h){
return (f(x+h)-f(x))/h;
}
class para_sin_plus_cos: public functor_base{
public:
para_sin_plus_cos(double p): alpha(p) {}
virtual double operator()(double x) const overrid{
return sin(alpha*x)+cos(x);
}
private:
double alpha;
};
구분된 모든 펑터는 functor_base에서 파생되어야 한다.
para_sin_plus_cos sin_1(1.0);
cout<<finite_difference(sin_1, 1., 0.001)<<endl;
double df1=finite_difference(para_sin_plus_cos(2.0), 1., 0.001),
df0=finite_differnece(para_sin_plus_cos(2.0), 0., 0.001);
OOP 방식의 functor 단점
- 성능: operator()는 항상 가상 함수로 호출된다.
- 적용 가능성: functor_base에서 파생된 클래스만 인수로 사용할 수 있다.

따라서 functor는 가능하면 제네릭 방식으로 구현하는 것이 좋다.
상속을 사용하면, 런타임에 함수를 선택할 수 있다는 장점이 있다.