Eat Study Love

먹고 공부하고 사랑하라

SW 만학도/C++

6. Out_of_class Definition & Operator Overloading

eatplaylove 2024. 4. 10. 22:24

https://eglife.tistory.com/49

 

5. Classes

https://eglife.tistory.com/43 4. Functions and Memory Management https://eglife.tistory.com/36 3. C++ Standard Library (3) https://eglife.tistory.com/35 2. C++ Standard Library (2) https://eglife.tistory.com/34 1. C++ Standard Library (1) 아 ~ 이게 무

eglife.tistory.com

 

 

 

갈수록 어려워지는 C++의 세계

 

Class 쪽으로 갈수록 뭔가 내용이 점점 많아진다.

 

그래도 말 그대로 내용이 많.아. 지는 것이지 솔직히 어렵지는 않다.

 

뭐든 익숙하지 않을 때 어려워 보이는 법이고, 막상 친해지면 언제 그랬냐는 듯 자연스럽게 그것에 녹아들기 마련

 

금일은 Class 밖에서 Definition을 때릴 경우와, C++의 꽃이라고 불리우는 Operator Overloading에 대한 학습을 진행해보자!

 

Out of Class Definition

 

template <typename T>
class SimpleVector{
    private:
        T* array;
        int size;
        int capacity;
        void resize();
    public:
        SimpleVector(int initialCapacity);
        SimpleVector(std::initializer_list<T> elements);
        ~SimpleVector();
        void addElement(T element);
        int getSize() const;
};

template <typename T>
SimpleVector<T>::SimpleVector(int initialCapacity) :
            size(0),capacity(initialCapacity)
            { array = new T[capacity]};

template <typename T>
SimpleVector<T>::SimpleVector(std::initializer_list<T> elements) :
            size(0),capacity(elements.size())
            { array = new T[capacity];
            for(auto element : elements){
                addElement(element);
                }
            }

template <typename T>
SimpleVector<T>::~SimpleVecto(){
    delete[] array;
}
template <typename T>
void SimpleVector<T>::addElement(T element){
    if (size == capacity)
    resize();
    array[size] = element;
    size++;
}

template <typename T>
int SimpleVector<T>::getSize() const{
    return size;
}

template <typename T>
void SimpleVector<T>::resize(){
    capacity *= 2;
    T* newArray = new[capacity];
    for(int i=0;i<size<i++){
        newArray[i] = array[i];
    delete[] array;
    array = newArray;
    }
}

 

 

- 일단 좀 희한한 것은 template <typename T>는 function을 정의해 줄 때마다 계속 상단에 추가해야한다.

 

- 아무래도 Class 밖에서 definition을 하다 보니까 그런가 보다.

 

 

※ 지난 시간 classes 목차에서는, 한 class 안에서 함수정의를하다보니 template <typename T>를 최초 한 번만 사용

 

template <typename T>
class SimpleVector{
    private:
        T* array;
        int size;
        int capacity;
        void resize();
    public:
        SimpleVector(int initialCapacity);
        SimpleVector(std::initializer_list<T> elements);
        ~SimpleVector();
        void addElement(T element);
        int getSize() const;
};

 

 

 

- Class Declaration 부분에선 1) data members 와 2) method의 prototype 들이 선언된다.

 

 

template <typename T>
void SimpleVector<T>::addElement(T element){
    if (size == capacity)
    resize();
    array[size] = element;
    size++;
}

template <typename T>
int SimpleVector<T>::getSize() const{
    return size;
}

template <typename T>
void SimpleVector<T>::resize(){
    capacity *= 2;
    T* newArray = new[capacity];
    for(int i=0;i<size<i++){
        newArray[i] = array[i];
    delete[] array;
    array = newArray;
    }
}

 

- 각 함수의 Implementation은 class body의 바깥쪽에서 구체적으로 선언이 된다. ( 위 3개 method 예시 )

 

- 위에서도 언급했듯이, 각 Method는 선언하기 전에 template declaration이 필요하다.

(template <typename 'ANYTHING YOU WANT'>

 

- Method 이름을 쓸 때마다, class name & template argument(여기선 T), scope resolution operator(::)를 써줘야 한다.

( SimpleVector (classname) <T> (template argument)  :: (scope resolution operator) )

 

- Class 안에 있는 attribute들은 specifier 없이 접근 가능하다. 편 - 안 !

 

 

Out-of-Class Definition Benefits - 대체 이 귀찮은 걸 왜 할까??

 

- Benefits

 

 1. 기본적으로 Class Interface를 깔끔하게 할 수 있다. --> Code 탐색 / 이해가 빨라짐

 

 2. Header file에 (여기선 Class) 함수 declare만 하고 header file을 linking해서 source file에 implementation(동작방법 서술)을 해놓기 때문에, 혹여나 Method에 변경점을 주고 싶을 때에 source file만 수정해서 recompile하면 된다. Header fiile에 대대적인 공사를 해서 recompile을 하는 수고가 덜어진다는 뜻!

정리하자면, 그냥 코드 유지/보수하기가 쉽다는 것이다.

 

- Note for Class Template?

 

- 앞서 설명했듯이 Template implementation이 필요하고 보통은 member function의 정의/수행 모두 header file안에서 진행한다... -> 솔직히 뭔 소린지 잘 모르겠다ㅠㅠ

 

 

Class Pointers

 

- 뭐,, class를 지칭하는 pointer라기 보단, 어떤 class의 object를 가르키는 pointer이다.

 

- Class Pointer Declaration 방법 4 가지

// 1) Declare a pointer without a real object
SimpleVector<int> *intVecPtr1 = nullptr;

// 2) Pointer points to an local object
SimpleVector<int> intvec(10);
SimpleVector<int> *intVecPtr2 = &intvec;

// 3) points and assign a dynampic(Heap에 할당) object
SimpleVector<int> *intVecPtr3 = new SimpleVector<int>(10);

// 4) Create a "new" object using an initializer list
SimpleVector<int> *intVecPtr4 = new SimpleVector<int>({1,2,3});
SimpleVector<int> *intVecPtr4 = new SimpleVector<int>{1,2,3};

위에서부터 1,2,3,4번 case

 

- 그냥 intVecPtr을 대뜸 선언한 경우와(nullptr// 1번 case )  Local Variable은 intVec을 만들어 놓고 그 주소를 pointing하는 경우( 2번 case ) 그리고 Dynamic하게 new를 통해 SimpleVector를 initializer list를 통해 만든 뒤에 그 주소를 pointing하는 경우를 그림으로 표현한 것이다.

 

 

- Dynamic Allocation and Deallocation

 

뭐,, Smart Pointer를 사용하면 function 끝 날 때 memory delete를 알아서 해주니까 좋다는 내용

 

 

Class Pointer Access

 

intVecPtr->addElement(10);
*intVecPtr.addELement(10);

 

 

- 이런식으로 접근한다.

 

- 접근을 할 때에는 Only Public Member만 접근이 가능하다.

 

 

Operator Overloading

 

- 괴애앵장히 중요한 내용이라고 한다. 사실 뭐.. 안 중요한 내용이 있는가;;

 

- Operator는 Compiler에서 수학적/논리적으로 특정 행동을 해달라고 요청하는 기호이다.

( ex) + , - , < , > , ==, * )

 

- 각 Class는 그들만의 operator 해석을 정의내릴 수 있다.

 

 

- 원래 <<, >> 꺽쇠표시도 bit shift였고 [ ] 도 array 전용인데 verctor에서도 array와 비슷하게 동작한다. 그리고 < , > 기호도 숫자에서만 비교하는 것인데 string에서 알파벳순으로 크기 비교가 가능하도록 재정의되었다.

 

- Operator Overloading이란, C++ operator를 cusomizing 가능하게 해주는 것이다.

 

- 다양한 Operator Overloading 사례를 살펴보자, 예시는 모두 SimpleVector로 다룬다.

 

 

template <typename T>
class SimpleVector{
    private:
        T* array;
        int size;
        int capacity;
        void resize();
    public:
        SimpleVector(int initialCapacity);
        // ~SimpleVector();
        void addElement(T element);
        int getSize() const;
        T& operator[](int index);
};

template <typename T> // operator 정의
T& SimpleVector<T>::operator[](int index){
    return array[index];
}

 

- 참고로 왜 다~ 알고 있는 operator를 구태여 overloading 해주나.. 했더만, 다~~ 알고 있는 건 인간들의 입장이고 컴퓨터는 의외로 다~~ 알지 못한다. 그래서 template class를 통해 만들어준 object들은 컴퓨터 입장에서 아예 새로운 type이라 얘들의 operator 상호작용을 일일히 다시 정의해주지 않으면 Compile 시 Error가 발생한다는 것

 

- 컴퓨터는 잘 길들여 놓으면 똑똑한데, 그 전까진 그냥 고철덩어리에 불과하지 않는다.

 

- 이렇게 operator를 선언해 줄 때에는, class의 body 안에도 prototype이 선언되어야 한다. 아니면 Error!

 

- 그리고 Compile 하다가 알아낸건데, Class 안에 private / public에서 declare 해 준 method를 class 내부 or Out of Class에서 정의해주지 않으면 Compile Error가 뜬다. Method를 선언했으면 Implementation까지 어디서든 해야한다는 뜻!

 

- 마지막으로, operor[] 의 경우 vec[0] = 3; 처럼 수정을 할 경우도 있기에 return 값을 Template T가 아닌 &T로 Call by Reference를 통한 Data Modification이 가능하게 해야한다.

(어차피 이것도 깜빡하면 compile error로 다 알려주긴 하더라 ㅎㅎ)

 

 

 

 

template <typename T>
class SimpleVector{
    private:
        T* array;
        int size;
        int capacity;
        void resize();
    public:
        SimpleVector(int initialCapacity); // 앞에 return 값 없으면 initialize
        SimpleVector(std::initializer_list<T> elements);
        // ~SimpleVector();
        void addElement(T element);
        int getSize() const;
        T& operator[](int index);
        SimpleVector<T> operator+(SimpleVector& rhs);

};

template <typename T> // operator 정의 operator 정의 operator 정의 operator 정의 operator 정의 operator 정의 
T& SimpleVector<T>::operator[](int index){
    return array[index];
}

template <typename T> //Operatpr +++++++++++
SimpleVector<T> SimpleVector<T>::operator+(SimpleVector& rhs){ //SimpleVector<T>를 해도 되지만, 그러면 그놈을 하나 더 만드는 거라서 메모리 낭비 .. -> & 로 객체복사 대신 원본객체에 참조만 해서 데이터를 가져온다.

    SimpleVector<T> result(size);   //여기서 result는 현재객체 vec3 = vec1+vec2 라고 했을때, vec3를 의미한다.(=this 사용가능)
    cout << size << endl; //5
    for(int i=0;i<size;i++){
        result.addElement(array[i]+rhs[i]);
    }
    return result;
}

 

- 이해하는데 시간이 좀 걸렸다 ㅠㅠ

 

- 일단, 궁금했던 것 CHAT GPT로 정리 --> 강사가 필요 없는 수준이다.. 물론 오답도 반환하겠지만 ㅋㅋ

 

   1) operator+(SimpleVector&) 이렇게 Reference를 받는 이유? :

 우리는 rhs의 값만 사용해서 더하기에 쓰고 싶은데, 그냥 SimpleVector<T>를 받게되면 SimpleVector<T> type의 rhs 객체를 하나 만들어줘야 한다. 그것은 메모리낭비! 그냥 SimpleVector(&)를 하면 원복객체에 data만 참조해서 가져오기 때문에 메모리효율이 올라간다. --> 물론 SimpleVector<T>를 그냥 써도 코드는 잘 돌아간다.

 

   2) result(size)에서 result는 뭐냐?:

 최종 객체를 말한다. 처음에 size 초기값을 컴파일러가 어떻게 알고 for문을 돌리는지 궁금했는데, vec3=vec1+vec2 이라고 할 때, vec3에 예상되는 size를 미리 계산으로 땡겨서 size값으로 설정하고 opeartor 정의를(for문 돌리기) 실현한다고 한다. 똑똑이!

 

  3) 그리고 for문 안에 더하기 실현하는 것에서 addElement(array + rhs)에서 array는 갑자기 뭔가? :

 vec1 + vec2 라고 했을 때, vec1 이 array다. + operator 앞에 잇는 놈을 그냥 array로 받더라.. 실제로 vec1 * 2 + vec2 를 했을 때 계산결과를 보고 알 수 있었다.

 

 

 

 

- 일일히 다 코드를 따라 쳐보려 했으나 여의치 않다.

 

template <typename T>
class SimpleVector{
    private:
        T* array;
        int size;
        int capacity;
        void resize();
    public:
        SimpleVector(int initialCapacity); // 앞에 return 값 없으면 initialize
        SimpleVector(std::initializer_list<T> elements);
        // ~SimpleVector();
        void addElement(T element);
        int getSize() const;
        T& operator[](int index);
        SimpleVector<T> operator+(SimpleVector& rhs);
        SimpleVector<T>& operator+=(SimpleVector<T>& rhs);
        // T& operator+=(SimpleVector<T>& rhs);


};

template <typename T> // +=operator +=operator +=operator +=operator +=operator +=operator +=operator 
SimpleVector<T>& SimpleVector<T>::operator+=(SimpleVector<T>& rhs){
    for(int i=0;i<size;i++){
        array[i] += rhs.array[i];
    }
    return *this;
}

 

 

- 일단 함수의 input이 SimpleVector<T>& 인 것과 T&의 차이는, 전자의 경우 객체 전체를 다루는 것이고 후자의 경우 index 하나하나에 해당하는 값을 다룬다.. 솔직히 잘 모르겠다 ㅠㅠ

 

- 하다보면 이해가 될라나..

 

 

++i 는 일단 증가하고 변수를 사용하는 것,  i++은 일단 변수를 먼저 사용하고, 변수를 증가시킨다.

 

- post 부분 정의할 때 (int) 해준 건 별 의미 없다. 그냥 post 구분용으로 적는 것으로 약속한 것.

 

- post는 pre에 비해 copy만드는 과정이 추가되어 일반적으로 시간소요가 더 걸린다.

 

- pre의 경우 return 값을 *array로 해도 된다. 그리고 Output을 SimpleVector<T>&가 아니라 SimpleVector<T>로 해도 똑같이 코드는 돌아가더라.

 

SimpleVector<T>& operator++()의 경우:
  • 참조 반환 (SimpleVector<T>&): 이 형태는 객체의 상태를 변경한 후, 변경된 객체 자체(*this)를 반환합니다. 이는 메모리 할당이나 복사가 없이 직접 객체에 대한 참조를 반환하기 때문에 성능상 유리합니다.
SimpleVector<T> operator++()로 변경한 경우:
  • 객체 반환 (SimpleVector<T>): 이 형태는 객체의 상태를 변경한 후, 변경된 객체의 복사본을 반환합니다. 이 방법은 추가적인 복사 생성자 호출이 발생하여 성능에 영향을 줄 수 있습니다. 이 복사본은 원본 객체와 동일한 상태를 갖지만, 원본 객체와는 별개의 객체입니다.
두 경우 모두 객체의 상태는 증가됩니다(++array[i]). 그러나 반환 방식의 차이는 메모리 사용과 성능에 영향을 미칩니다. 참조를 반환하는 방식(SimpleVector<T>&)은 메모리와 성능 측면에서 더 효율적입니다. 복사본을 반환하는 방식(SimpleVector<T>)은 메모리 사용량이 더 많고, 복사 생성자가 호출되어 성능이 다소 저하될 수 있습니다.
따라서, 특별한 이유가 없는 한 전위 증가 연산자에서는 일반적으로 참조를 반환하는 것이 바람직합니다. 이는 연산자 체이닝에도 유용하며, C++의 일반적인 관례에 부합합니다.

 

 

이유는 위와 같다고 한다;;

 

 

- Reference를 반환하기에 Chaining 이 가능하다.

 

- <<를 통해 1,2,3,4,5 등 변수를 직접 넣으려면 const를 써줘야 한다.(Template안 정의 참고)

(그냥 변수를 넣는건 괜찮다. ex) int a = 3; 요런 거에서 a를 넣는 case )

 

 

 

 

- > , < Operator는 이 경우엔 vec 내부 객체수로 비교한다 = > Size!

 

 

 

- Compiler가 SimpleVector라는 type을 자동으로 bool 형태로 만들어버리는 Case이다.

 

- 이미 Python에는 구현되어 있다. Cin의 경우에도 정상동작 -> True , Error -> False로 변경될 수 있게 이미 구현이 되어있다.

 

- bool operator의 경우엔 굳이 classl 내에 함수 declar 할 때 앞에 bool을 prefix 할 필요가 없다.(Return의 tyep) 그냥 아래와 같이 간단하게 prototype만 적어준다.

operator bool() const;

(const 빼먹으면 Error뜬다;)

 

 

 

 

- 첫 번째 줄 코드의 경우, return array; 이고 output이 T*이므로 array의 시작부분을 포인팅하는 Pointer를 반환한다.

 

- auto는 그것들을 자동으로 dereference 해줌 ( * pointer )

 

 

- 개념이 너무 어렵다 어려워.. 계속 고민해보자--> 특히 T*을 사용하는데, T&을 사용하지 않는 건 왜일까.. 계속 고민해보자

 

 

 

Operator Overloading as Non-Member Functions

 

- 지금까진 overload 기능을 class 내부에서 memer function으로만 정의했는데, global scope으로도 정의 가능하다.

 

 

 

- 기존과 달리 operator 정의해줄 때 SimpleVector<T> PREFIX :: 를 해주지 않았다. 이것은 SimpleVector에 종속되지 않고 일반적인 함수라는 뜻이다.

 

 

- "Hello"는 string object가 아니라서 method에 접근할 수 없었다.(Hello = literal) 그러나 수정 후엔 Hello가 literal 인데도 string으로 쳐준다. Non-Member로 정의가 되어있다면 말이다!!

 

- 근데 뭐,, 생각해보니 별거 없는 거 같다. 그냥 string, SimpleVector.. 등으로 같은 category로 정의된 놈들끼리 operator 동작할 수 있게 해준다는 거네

 

 

 

- 그 밖에 operator로 쓸 수 있는 다양한 특수기호들..

 


 

발가벗겨진 기분이다.

 

머리가 꼬이는 거 같다. 왜케 이해가 안 되지 ㅋㅋㅋ 에효 뭐 어떻게든 되겠지~

 

 

 

'SW 만학도 > C++' 카테고리의 다른 글

8. Inheritance - Basics  (0) 2024.04.12
7. Copy&Move, Special Members  (2) 2024.04.12
5. Classes  (0) 2024.04.09
4. Functions and Memory Management  (1) 2024.03.29
3. C++ Standard Library (3)  (1) 2024.03.24