불변 객체의 주요 특징

 

함수형 프로그래밍의 특징인 불변성을 따르려면 지역 변수를 변경하면 안되고 함수 내에서 전역 변수에 접근하면 안된다.

생성 이후 상태를 바꿀 수 없는 객체를 의미한다.

 

지역 변수 수정

int mutableVar = 100;
for (int i = 0; i <= 10; ++i) mutableVar += i;

 

mutableVar는 여러 번 수정되는 가변 객체로 다뤄지기 때문에 지역 변수를 변경할 수 없다는 규칙에 위배된다.

 

int mutableVar = 100;

int mutableVar0 = mutableVar + 0;
int mutableVar1 = mutableVar0 + 1;
...
int mutableVar10 = mutableVar9 + 10;

 

모양새가 웃기긴 하지만 모든 변수들은 초기화 이후 수정되지 않았기 때문에 불변 객체이다.

 

 

함수에 전달된 인수 수정하기

함수에 값으로 전달된 인수는 복사본이 전달된 것이라서 원본이 변하지 않는다.

만약 원본을 수정하고 싶다면 참조를 전달하면 되고, 데이터를 직접 참조로 넘기기보다는 데이터를 담은 클래스나 구조체를 넘겨서 수정하는 것이 더 좋다.

 

 

 

 

값 수정 금지하기

 

불변성의 핵심 요소는 값 수정을 막는 것이다.

 

class MyAge {
public:
    const int age;
    MyAge(cosnt int initAge = 20) : age(initAge) {}
};

MyAge ageNow, ageLater(8);
ageNew.age = 10; // error

 

const 키워드를 사용하면 값의 수정을 막을 수 있다.

const 키워드는 함수 동작에 불변성을 제공해줌과 동시에 클래스 안의 변수가 수정되지 않음을 확신할 수 있다.

 

 

 

 

불변 객체에 일급 함수와 순수 함수 적용하기

 

class MyValue {
public:
    const int value;
    MyValue(int v) : value(v) {}
};

class MyFunction {
public:
    const int x, y;
    MyFunction(int _x, int _y) : x(_x), y(_y) {}
    MyValue addition() const { ... } // 순수 함수
    MyValue subtraction() const { ... }
    MyValue multiplication() const { ... }
    MyValue division() const { ... }
};

int a = 100;
int b = 10;

MyFunction func(a, b); // 객체 생성

 /* 멤버 함수 객체(std::function) 생성. 일급 함수 */
auto callableAdd = std::mem_fn(&MyFunction::addition);
auto callableSub = std::mem_fn(&MyFunction::subtraction);
auto callableMul = std::mem_fn(&MyFunction::multiplication);
auto callableDiv = std::mem_fn(&MyFunction::division);

/* 멤버 함수 호출 */
auto value1 = callableAdd(func);
auto value2 = callableSub(func);
auto value3 = callableMul(func);
auto value4 = callableDiv(func);

 

std::mem_fn 함수로 MyFunction 클래스의 멤버 함수를 일급 함수로 만든다.

각 멤버 함수들은 동일한 입력에 대해 항상 같은 결과를 반환하는 순수 함수이고 MyValue의 value는 const로 선언되어서 값을 변경할 수 없다.

 

 

 

 

불변 객체 구현하기

 

가변 객체 만들기

클래스의 일반적인 형태로 멤버 변수는 private, 접근 함수(get/set)를 public으로 선언하는 경우 OOP 규칙은 준수되지만 인스턴스화 된 이후에도 값이 언제나 수정될 수 있기 때문에 가변적이다.

 

 

가변 객체를 불변 개체로 변환하기

멤버 변수와 함수에 const 키워드를 부여하고 set 함수를 제거함으로써 불변성을 부여한다.

다만 이런식으로 불변성을 부여하게 되면 멤버 변수를 수정할 수 있는 방법이 없다.

 

값을 수정하면서 불변성을 유지하는 방법중에 새로운 인스턴스를 생성하고 반환하는 방법이 있다.

 

class ImmutableEmployee {
public:
    ImmutableEmployee(const int& id, const std::string& firstName, ...)
    : m_id(id), m_firstName(firstName), ... {}
    const ImmutableEmployee SetId(const int& id) const {
        return ImmutableEmployee(id, m_firstName, ...); // 새로운 인스턴스를 반환한다
    }
    ...
private:
    const int m_id;
    const std::string m_firstName;
    ...
};

ImmutableEmployee me(0, first, last, d);

ImmutableEmployee me2 = me.SetId(1);
ImmutableEmployee me3 = me.SetFirstName("Alexis");
...

 

맨 처음 모양새가 웃기다고 했었던 코드와 매우 유사한 형태를 띈다.

추가로 읽기용 함수는 값 대신 const 참조를 반환하는것이 좋다.

 

 

 

 

불변성의 장점

 

함수형 프로그래밍의 핵심 요소는 불변 객체이다. 불변 객체를 통해 얻을 수 있는 이득은 다음과 같다.

 

◾ 외부 상태가 변경되지 않으므로 부작용을 없앨 수 있다. 만약 변경이 필요하다면 새로운 객체 인스턴스를 생성해서 객체 내부의 값을 수정할 수 있다.

◾ 잘못된 객체 상태가 존재하지 않는다. 왜냐면 현재 객체의 상태를 바꾸는게 아니라 변경할 값으로 새로운 객체를 만들기 때문이다.

◾ 잠금없이 여러 개의 함수를 함께 실행할 수 있기 때문에 스레드에 안전하다(thread-safe). 즉, 동기화 이슈를 피할 수 있다.

+ Recent posts