데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      [Python] C++과 Python의 Class 멤버 관리

      Teus 22.07.11
      1,980 8 0

      안녕하세요. Teus입니다.

      이번 포스팅은 같은 조상을 두고있는 두 언어인 C++과 Python의 Class Propety 관리에 대해서 다룹니다.

      python_11.png

      1. 왜 C++이랑 Python을 비교하는가?

      최근에 C++을 다시 공부할 일이 있었습니다.

      • C++ : C언어의 생산성을 높임(물론 컴파일러가 조금 다름)

      • Python : C언어를 가지고 CPython을 만들어서 생산성을 높임

      결국, 제가 느낀 바로는 둘다 C를 Base로 생산성이 높이는 방향으로 개선되었다고 느꼈습니다.

      두 언어에서 가장 큰 차이점이라 하면 역시 메모리 관리고, 이 메모리 관리 부분에서 Python과 C++의 Class를 관리하는 난이도가 달라지게 됩니다.


      2. C++의 Class 생성자

      C++은 C언어의 대부분 문법을 이어 받아서 Malloc 및 New연산자를 통해서 프로그래머가 직접 Memory관리를 합니다.

      하지만, 큰 힘에는 큰 책임이 따릅니다.

      메모리를 동적으로 관리하는 만큼, 이 메모리를 사용하지 않을 경우 적절하게 메모리 해제가 이뤄져야 합니다.

      문제는 포인터를 Class의 Property로 사용할때 발생합니다.


      아래 예시를 보시죠

      #include <iostream>
      
      using namespace std;
      
      class temp_cls
      {
          public:
          temp_cls(){
              pro_a = new int(5);
              printf("temp object created\n");
          }
          ~temp_cls(){
              delete pro_a;
              printf("temp object removed");
          }
          int *pro_a;
      };
      
      int main()
      {
          temp_cls test;
          temp_cls *temp = &test;
          temp_cls *temp2(temp);
          cout << (*temp).pro_a << endl;
          cout << (*temp2).pro_a << endl;
          delete temp;
          printf("test val %p",(*temp2).pro_a);
          return 0;
      }
      /*
      temp object created
      a.b와 b.b가 동일한 위치를 Pointing하고 있는것을 볼 수 있습니다
      0x56502926aeb0
      0x56502926aeb0
      double free or corruption (out)
      */

      위 코드는 아래처럼, temp는 test를 가리키고, temp2는 temp를 복사해 오게 됩니다.

      163932_1.png

      문제는 C++의 복사생성자는 단순히 Property를 대입하는 형식으로 일어납니다.

      때문에 temp->pro_a와 temp2->pro_a가 가리키는 메모리가 동일한 위치가 됩니다.

      그러면, 프로그램이 종료되면서 temp와 temp2가 순서대로 메모리 해제가 되고, 이때 temp의 소멸자가 먼저 실행되면서 temp->pro_a도 메모리 해제가 일어납니다.

      하지만 temp2의 소멸자가 불린다면? 이미 해제된 메모리를 다시 해제하게 되면서 에러가 발생하게 되죠.


      2_1. 그래서 여러가지의 생성자

      C++는 분명 C언어 대비 생산성을 높인 언어입니다.

      하지만, C언어의 동적 메모리할당을 그대로 유지하기 위해서는 위와같은 문제에 직면하게 됩니다.

      때문에 C++의 경우 아래와같이 여러가지 생성자가 필요합니다.

      1. 일반 생성자

      2. 복사 생성자

      3. 대입 연산자

      4. 변환 생성자

      5. 그 외에 Input Parameter의 종류에 따라....

      덕분에, C 대비 생산성이 높아진것 맞지만 기존 JS나 Python과 같은 언어를 사용하던 유저에게는 역시 어렵다고 느껴질 수 밖에 없습니다.


      아래는 다양한 생성자를 구현하는 예시 입니다.

      #include <iostream>
      
      using namespace std;
      
      class temp_cls
      {
          public:
          temp_cls(void){
              pro_a = new int(5);
              printf("temp object %p created\n", this);
          }
          //int value를 받을 때 대응될 변환생성자
          explicit temp_cls(int a){
              this->pro_a = new int(a);
              printf("temp object %p created by trasnform init\n", this);
          }
          //clss object를 받을 경우의 복사생성자
          explicit temp_cls(const temp_cls &tg_obj){
              printf("temp object %p created\n", this);
              printf("receive temp_cls object. tg_obj a : %d\n", *tg_obj.pro_a);
              this->pro_a = new int (*tg_obj.pro_a);
          }
          //class pointer를 받을 경우의 복사생성자
          explicit temp_cls(temp_cls *tg_obj){
              printf("temp object %p created\n", this);
              printf("receive temp_cls pointer. tg_obj a : %d\n", *(*tg_obj).pro_a);
              this->pro_a = new int(*(tg_obj->pro_a));
          }
          ~temp_cls(){
              delete pro_a;
              printf("temp object %p removed\n", this);
          }
          //class object간 =연산을 통해서 복사할 경우
          temp_cls &operator =(const temp_cls &tg_obj){
              printf("temp object %p created\n", this);
              printf("receive temp_cls object by operator '='. tg_obj a : %d\n", *tg_obj.pro_a);
              this->pro_a = new int (*tg_obj.pro_a);
              return *this;
          }
      
          int *pro_a;
      };
      
      int main()
      {
          temp_cls test123;
          temp_cls test(10);
          temp_cls test_op(30);
          test_op = test;
          temp_cls *temp = new temp_cls(test);
          temp_cls *temp2 = new temp_cls(temp);
          cout << (*temp).pro_a << endl;
          cout << (*temp2).pro_a << endl;
          delete temp;
          printf("test val %p is alive \n",(*temp2).pro_a);
          return 0;
      }
      /*
      (test123 obj)temp object 0x7ffe49d54740 created
      (test obj)temp object 0x7ffeb6649a68 created by trasnform init
      (test_op obj)temp object 0x7ffeb6649a70 created by trasnform init
      (test_op obj =operation)temp object 0x7ffeb6649a70 created
      (test_op obj =operation)receive temp_cls object by operator '='. tg_obj a : 5
      (temp pointer)temp object 0x55954cd76320 created
      (temp pointer)receive temp_cls object. tg_obj a : 5
      (temp2 pointer)temp object 0x55954cd76360 created
      (temp2 pointer)receive temp_cls pointer. tg_obj a : 5
      0x55954cd76340
      0x55954cd76380
      (temp pointer)temp object 0x55954cd76320 removed
      (temp2 pointer)test val 0x55954cd76380 is alive 
      (test pointer)temp object 0x7ffeb6649a70 removed
      (test_op pointer)temp object 0x7ffeb6649a68 removed
      */


      3. Python의 Class 생성자

      Python의 경우 상대적으로 간단합니다.

      다양한 생성자가 아닌 오직 기본생성자만 지원합니다.

      class temp_cls:
          def __init__(self, a=None, b=None):
              self.a = a
              self.b = b
      
      import copy
      if __name__ == "__main__":
          temp = temp_cls(1, [20,54,62,3,9])
          #C++의 temp_cls temp2(temp);
          #를 얕은복사 형식으로 사용한것과 같은 기능을 합니다.
          temp2 = copy.copy(temp)    
          print(temp is temp2)
          print(temp.b is temp2.b)
          del temp
          print(temp2.b)
      '''
      temp과 temp2가 같은지 여부 : False
      temp.b와 temp2.b가 같은지 여부 : True
      temp를 삭제한 뒤 temp2.b : [20, 54, 62, 3, 9]
      '''

      Python의 경우 복사생성자를 copy Library가 대신합니다.

      기본적으로 Shallow Copy가 이뤄지면서, C++와 마찬가지로 2개의 Instance(temp, temp2)가 동일한 메모리 주소를 보게 됩니다.

      하지만, Python의 경우 메모리를 GC(Garbage Collection)을 통해서 관리합니다.

      GC는 변수의 Reference Count를 관리합니다. 덕분에, 아래처럼 별도의 메모리 관리 없이 mutable 객체를 Class Property로 사용할수가 있죠

      163932.png


      물론, 위와같은 복사동작은 copy Library를 통해서 이뤄지고, 이 copy Library에서는 아래와 같은 방법으로 Copy를 진행합니다.

      출처 : https://github.com/python/cpython/blob/17524b084b565b321b4671f50a62864ea907b24f/Lib/copy.py#L66

      '''
      type의 종류에 따라 copy function을 정의
      list, dict, set, bytearray는 PyListObject처럼
      CPython에서 정의된 copy방법을 사용
      PyList의 경우
      static PyObject *
      list_copy_impl(PyListObject *self)
      {
          return list_slice(self, 0, Py_SIZE(self));
      }
      
      이 외에 immutable Object의 경우
      함수의 매개변수로 투입 -> 합수의 return으로 반환
      의 방법으로 객체의 얕은복사
      '''
      _copy_dispatch = d = {}
      
      def _copy_immutable(x):
          return x
      for t in (type(None), int, float, bool, complex, str, tuple,
                bytes, frozenset, type, range, slice, property,
                types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented),
                types.FunctionType, weakref.ref):
          d[t] = _copy_immutable
      t = getattr(types, "CodeType", None)
      if t is not None:
          d[t] = _copy_immutable
      
      d[list] = list.copy
      d[dict] = dict.copy
      d[set] = set.copy
      d[bytearray] = bytearray.copy
      
      if PyStringMap is not None:
          d[PyStringMap] = PyStringMap.copy
      
      
      def copy(x):
          """Shallow copy operation on arbitrary Python objects.
          See the module's __doc__ string for more info.
          """
          #복사하는 Class의 Type 식별
          cls = type(x)
          
          copier = _copy_dispatch.get(cls)
          if copier:
              return copier(x)
      
          if issubclass(cls, type):
              # treat it as a regular class:
              return _copy_immutable(x)
      
          copier = getattr(cls, "__copy__", None)
          if copier is not None:
              return copier(x)
      
          reductor = dispatch_table.get(cls)
          if reductor is not None:
              rv = reductor(x)
          else:
              reductor = getattr(x, "__reduce_ex__", None)
              if reductor is not None:
                  rv = reductor(4)
              else:
                  reductor = getattr(x, "__reduce__", None)
                  if reductor:
                      rv = reductor()
                  else:
                      raise Error("un(shallow)copyable object of type %s" % cls)
      
          if isinstance(rv, str):
              return x
          return _reconstruct(x, None, *rv)

      결국 Python은 조금 더 Cost가 들지만, User가 사용하기에는 보다 편리한 문법을 제공하는것을 볼 수 있습니다.


      4. 정리

      최신까지는 아니지만, C++ 11에서 shared_ptr이라는 Smart Pointer가 생깁니다.

      해당 포인터는 Python의 GC처럼 shared_ptr이 가리키고 있는 대상의 Reference Count를 측정하고, 모든 shared_ptr이 해제될 경우 그때 해당 shared_ptr이 가리키는 대상이 사라집니다.

      C를 Base로 두 언어의 방향성이 달랐지만, 이후에 shared_ptr처럼 비슷한 기능이 구현되는 부분이 흥미로워서 이렇게 포스팅을 남깁니다.


      즐거운하루되세요 :)

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      Teus 님의 최신 블로그

      더보기
      동영상 기고하기