TIL_064 : memset, 절차/객체/데이터 지향 프로그래밍, virtual

김펭귄·2025년 11월 18일

Today What I Learned (TIL)

목록 보기
64/94

오늘 학습 키워드

  • memset

  • Procedural Programming

  • Object Oriented Programming

1. memset

  • 이전 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값으로 초기화 된다

2. Procedural Programming

  • 초기 프로그래밍은 절차지향적 프로그래밍 방식으로 프로그래밍 되었음

  • 함수(Procedure)들을 만들어 순차적으로 처리하는 방식으로, 프로그램은 문제 해결을 위해 절차(순서)를 따르며, 명령과 함수 호출을 중심으로 구성

  • 초기 프로그램은 크기가 작고, 처리 능력도 제한적이라 절차지향적 방법이 적합했음

  • 하지만, 프로그램의 크기가 커지고 복잡도가 증가하면서 유지보수와 재사용의 문제, 유연성 부족 등의 한계에 직면해 객체지향형 프로그래밍이 등장

3. Object Oriented Programming(OOP)

  • 프로그램을 단순히 데이터와 처리 방법으로 나누는 것이 아니라, 프로그램을 수많은 '객체(object)'라는 기본 단위로 나누고 이 객체들이 서로 상호작용하며 문제를 해결하는 방식

  • 객체란 하나의 역할을 수행하는 함수와 데이터의 묶음

  • 코드의 재사용성, 유지보수성, 유연성을 크게 향상시킴

OOP의 특징

1. 캡슐화

  • 하나의 역할을 수행하기 위해 필요한 함수와 데이터를 감쌈

  • 이 중에서, 필요한 데이터와 함수만 외부에 노출하여 각 객체간의 결합도를 낮춤

  • 외부에서 데이터에 바로 접근하지 못하게 하고, getter나 setter를 이용하여 안전성을 부여

  • 데이터를 외부에 노출시켰을 시, 어느 객체에서든 해당 데이터에 접근하여 마음대로 값을 수정할 수 있게 되고, 이는 언제 어디서 값이 변경되었는지 찾기가 힘들어짐

  • getter를 이용하면, 값에 대한 접근 없이 값만을 제공가능

  • setter의 경우, 값을 수정하려고 값이 들어올 때, 해당 값에 대한 신뢰성을 검증할 수 있으며, 객체 내부적으로 검증 결과를 구현할 수 있어 값 변화에 대한 제어가 가능해짐

2. 추상화

  • 여러 객체에서 공통적으로 사용되는 속성이나 기능을 묶는 것

  • 실제 내부 구현은 숨기고, 인터페이스만 외부로 노출하여 외부 이용자가 해당 동작은 몰라도 사용할 수 있도록 하는 것

3. 상속

  • 부모클래스의 특성과 기능을 물려받고, 새로운 기능과 데이터가 추가된 자식클래스를 생성

  • 캡슐화를 유지하며, 재사용이 가능하여 코드의 중복을 없앰

4. 다형성

  • Override(오버라이딩)
    자식클래스에서 부모클래스의 메소드를 뒤집어 씌어, 재정의하는 것

  • 오버로딩
    같은 이름의 함수가 인자의 개수나 자료형에 따라 다른 기능을 수행하는 것

OOP의 5원칙 (SOLID)

SRP (단일 책임 원칙)

  • 각 클래스는 하나의 역할만을 해야함

  • student클래스는 학생의 정보를 저장하는 역할만 하면 되지, 이외의 함수는 학생의 역할이 아님

  • 하나의 클래스에서 여러 역할을 다 수행하게 되면, 갓클래스가 되어 무거워지고 유지보수와 디버깅이 어려워짐

  • 따라서 점수 계산과 학생정보출력하는 부분은 따로 클래스를 만들어 각 클래스에서 student클래스를 받아서 사용

OCP (개방 폐쇄 원칙)

  • 확장에는 열려있고, 수정에는 닫혀있도록 코드 짜기

  • 기능이 변하거나 확장되는 것은 가능하지만 그 과정에서 기존의 코드가 수정되지 않아야 함

class ShapeManager {
public:
    void drawShape(int shapeType) {
        if (shapeType == 1) { /*원 그리기*/ } 
        else if (shapeType == 2) { /*사각형 그리기*/  }
    }
};
  • 위와 같이 짤 경우 새로운 도형을 추가할 때 drawShape함수도 수정을 해줘야함

  • 따라서 위와 같이 Shape라는 interface를 만들어서 새로운 도형 class가 추가되어도 다른 클래스에는 영향이 없게 만들기
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(); }
};

LSP (리스 코프 치환 원칙)

  • 자식 클래스는 부모 클래스에서 기대되는 행동을 보장해야 함

  • 즉, 부모 클래스를 사용하는 코드가 자식 클래스로 대체되더라도 정상적으로, 일관적으로 동작해야 함

  • 예를 들어, 정사각형이 직사각형의 자식클래스일 때, 어차피 정사각형은 높이와 너비가 같다고 정사각형의 setWidth함수에서 heigt까지 설정하면 안 됨.
    원래 부모클래스에서의 setWidth함수는 너비만 설정하는 함수였으므로.

Rectangle* Rect = new Square;
Rect->setWidth(10);		// 이렇게 부모클래스로 업캐스팅하여 사용했을 때 문제 없어야함

ISP (인터페이스 분리 원칙)

  • SRP에서 클래스의 단일책임을 강조한 것처럼, ISP는 인터페이스의 단일 책임을 강조

  • 하나의 인터페이스가 여러 책임을 하지말고, 분리해서 단일 책임을 갖도록 하는 원칙

  • 다른 말로, 하나의 객체가 사용하지도 않는 여러 기능을 가진다면, 각 기능들을 인터페이스로 분리하고 객체는 이 인터페이스를 사용하는 방식으로 설계하는 원칙

  • 예를 들어, 프린터기와 스캐너기능을 가지는 Machine객체를 만든다 했을 때, 프린터기와 스캐너는 별개이기도 하고, 나중에 프린터기능만 쓰는 Machine이 있을 수도 있으므로 두 기능은 분리해야함

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

DIP (의존 역전 원칙)

  • 상위 모듈이 하위 모듈에 의존해서는 안 됨

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

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

  • 즉 상위모듈은 하위모듈을 직접사용하지 않고, 하위모듈의 추상화클래스(인터페이스)를 사용

4. virtual

  • 저번에 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; }
};
profile
반갑습니다

0개의 댓글