memset
Procedural Programming
Object Oriented Programming
이전 new, malloc에 대해 공부해보면서 메모리를 한 번에 초기화하는 방법은 없을까 찾아봄
memset은 해당 메모리를 원하는 값으로 한 번에 초기화 가능한 함수
단, 할당받은 메모리에만 한하며, 반환값은 해당 메모리의 시작 주소를(void*) 반환
1바이트 단위로 초기화를 해주므로, 아래와 같은 실수 주의하기(char형이나 0으로만 초기화하기)
int arr[10];
int* ptr;
ptr = (int*) memset(arr, 1, sizeof(int) * 10);
// 각 배열원소가 1로 초기화되는게 아니라 1byte단위로 초기화됨
// 근데 int는 4byte이므로 01'01'01'01로 초기화되어 16843009값으로 초기화 된다
초기 프로그래밍은 절차지향적 프로그래밍 방식으로 프로그래밍 되었음
함수(Procedure)들을 만들어 순차적으로 처리하는 방식으로, 프로그램은 문제 해결을 위해 절차(순서)를 따르며, 명령과 함수 호출을 중심으로 구성
초기 프로그램은 크기가 작고, 처리 능력도 제한적이라 절차지향적 방법이 적합했음
하지만, 프로그램의 크기가 커지고 복잡도가 증가하면서 유지보수와 재사용의 문제, 유연성 부족 등의 한계에 직면해 객체지향형 프로그래밍이 등장
프로그램을 단순히 데이터와 처리 방법으로 나누는 것이 아니라, 프로그램을 수많은 '객체(object)'라는 기본 단위로 나누고 이 객체들이 서로 상호작용하며 문제를 해결하는 방식
객체란 하나의 역할을 수행하는 함수와 데이터의 묶음
코드의 재사용성, 유지보수성, 유연성을 크게 향상시킴
하나의 역할을 수행하기 위해 필요한 함수와 데이터를 감쌈
이 중에서, 필요한 데이터와 함수만 외부에 노출하여 각 객체간의 결합도를 낮춤
외부에서 데이터에 바로 접근하지 못하게 하고, getter나 setter를 이용하여 안전성을 부여
데이터를 외부에 노출시켰을 시, 어느 객체에서든 해당 데이터에 접근하여 마음대로 값을 수정할 수 있게 되고, 이는 언제 어디서 값이 변경되었는지 찾기가 힘들어짐
getter를 이용하면, 값에 대한 접근 없이 값만을 제공가능
setter의 경우, 값을 수정하려고 값이 들어올 때, 해당 값에 대한 신뢰성을 검증할 수 있으며, 객체 내부적으로 검증 결과를 구현할 수 있어 값 변화에 대한 제어가 가능해짐
여러 객체에서 공통적으로 사용되는 속성이나 기능을 묶는 것
실제 내부 구현은 숨기고, 인터페이스만 외부로 노출하여 외부 이용자가 해당 동작은 몰라도 사용할 수 있도록 하는 것
부모클래스의 특성과 기능을 물려받고, 새로운 기능과 데이터가 추가된 자식클래스를 생성
캡슐화를 유지하며, 재사용이 가능하여 코드의 중복을 없앰
Override(오버라이딩)
자식클래스에서 부모클래스의 메소드를 뒤집어 씌어, 재정의하는 것
오버로딩
같은 이름의 함수가 인자의 개수나 자료형에 따라 다른 기능을 수행하는 것

student클래스는 학생의 정보를 저장하는 역할만 하면 되지, 이외의 함수는 학생의 역할이 아님
하나의 클래스에서 여러 역할을 다 수행하게 되면, 갓클래스가 되어 무거워지고 유지보수와 디버깅이 어려워짐

확장에는 열려있고, 수정에는 닫혀있도록 코드 짜기
기능이 변하거나 확장되는 것은 가능하지만 그 과정에서 기존의 코드가 수정되지 않아야 함
class ShapeManager {
public:
void drawShape(int shapeType) {
if (shapeType == 1) { /*원 그리기*/ }
else if (shapeType == 2) { /*사각형 그리기*/ }
}
};

class Shape {
public:
virtual void draw() = 0; // 순수 가상 함수
};
class Circle : public Shape {
public:
void draw() {/*원 그리기*/}
};
class Square : public Shape {
public:
void draw() {/*사각형 그리기*/}
};
class Triangle : public Shape {
public:
void draw() {/*삼각형 그리기*/}
};
class ShapeManager {
public:
void drawShape(Shape& shape) { shape.draw(); }
};
자식 클래스는 부모 클래스에서 기대되는 행동을 보장해야 함
즉, 부모 클래스를 사용하는 코드가 자식 클래스로 대체되더라도 정상적으로, 일관적으로 동작해야 함
예를 들어, 정사각형이 직사각형의 자식클래스일 때, 어차피 정사각형은 높이와 너비가 같다고 정사각형의 setWidth함수에서 heigt까지 설정하면 안 됨.
원래 부모클래스에서의 setWidth함수는 너비만 설정하는 함수였으므로.

Rectangle* Rect = new Square;
Rect->setWidth(10); // 이렇게 부모클래스로 업캐스팅하여 사용했을 때 문제 없어야함
SRP에서 클래스의 단일책임을 강조한 것처럼, ISP는 인터페이스의 단일 책임을 강조
하나의 인터페이스가 여러 책임을 하지말고, 분리해서 단일 책임을 갖도록 하는 원칙
다른 말로, 하나의 객체가 사용하지도 않는 여러 기능을 가진다면, 각 기능들을 인터페이스로 분리하고 객체는 이 인터페이스를 사용하는 방식으로 설계하는 원칙
예를 들어, 프린터기와 스캐너기능을 가지는 Machine객체를 만든다 했을 때, 프린터기와 스캐너는 별개이기도 하고, 나중에 프린터기능만 쓰는 Machine이 있을 수도 있으므로 두 기능은 분리해야함

아래와 같이 인터페이스로 분리하여 사용하는 것이 좋다

상위 모듈이 하위 모듈에 의존해서는 안 됨
아래 예시처럼, 상위 모듈인 Computer에서 하위 모듈인 Keyboard, Monitor를 직접적으로 타입을 받아서 사용하면 나중에 입출력 부품이 바뀌거나 하는 경우에 유지보수에 어려움이 생김

따라서 마찬가지로 입출력 인터페이스 클래스를 따로 생성하고, 상위 모듈인 Computer에서는 이 인터페이스의 "포인터"를 받아서 결합력(의존성)을 낮춘다.

즉 상위모듈은 하위모듈을 직접사용하지 않고, 하위모듈의 추상화클래스(인터페이스)를 사용
저번에 virtual키워드 없이 부모클래스의 함수를 자식클래스에서 override하면 에러 발생한다 하였으나 아님
대신 override가 아니라, 함수 은닉이 발생하여 부모클래스의 함수가 은닉될 수 있다
virtual은 오버헤드 발생할 수 있다하였지만, 사실 요즘 cpu랑 캐시가 이정도는 감당 가능
그럼에도 불구하고 프로파일링 해봤는데 virtual이 병목지점이면 아래와 같이 해결
template<typename Derived>
class Animal {
public:
void speak() { static_cast<Derived*>(this)->speak_impl(); }
};
class Dog : public Animal<Dog> {
public:
void speak_impl() { cout << "Dog barks" << endl; }
};