-
아두이노 스마트 포인터 구현아두이노 2023. 2. 10. 01:44
스마트 포인터가 아두이노에 있으면 편리할 것 같아 구현해보았다. 아두이노는 일반적인 컴퓨터에 비해 렘과 저장공간이 부족하므로 메인기능과 약간에 플러스 알파만 구현하였다.
unique_ptr: 하나의 포인터만 객체를 참조할 때 사용
shared_ptr: 하나의 객체를 여러 포인터가 참조할 때 사용(레퍼런스 카운트가 0이되어야 할당이 해제된다.)
week_ptr: share_ptr이 가르키는 것을 공유하지만 레퍼런스 카운트는 증가시키지 않음
*shared_ptr이 서로를 참조하면, 카운트가 절대 0이 되지 않기 때문에 계속 메모리에 남아있게 된다. shared_ptr A를 선언하고, 선언한 shared_ptr을 복사한 week_ptr B을 선언한 다음, shared_ptr A를 가르킬 일이 있을 때 대신 week_ptr B를 참조하면 순환 참조를 피할 수 있다.
/* Smart Pointer 구현 참고: https://min-zero.tistory.com/entry/C-STL-1-3-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%8A%A4%EB%A7%88%ED%8A%B8-%ED%8F%AC%EC%9D%B8%ED%84%B0smart-pointer https://uncertainty-momo.tistory.com/48 https://8bitscoding.github.io/cpp-im-move-make/ https://colinch4.github.io/2020-01-01/16_%EC%9D%B4%EB%8F%99-%EC%83%9D%EC%84%B1%EC%9E%90-%EB%B0%8F-%EC%9D%B4%EB%8F%99-%EB%8C%80%EC%9E%85-%EC%97%B0%EC%82%B0%EC%9E%90/ https://pyoungon.tistory.com/117 */ #ifndef SMARTPTR #define SMARTPTR template<typename T> T&& move(T& object) { return static_cast<T&&>(object); //c++ 스타일 타입캐스팅 }; template<class T> class unique_ptr { private: T* ptr; public: explicit unique_ptr(T* p) :ptr(p) {}; //생성자의 인수가 한 개면 컴파일러가 암묵적 형변환 할 수 도 있는데 explicit으로 방지 unique_ptr(unique_ptr<T>&& origin) noexcept =default; //이동 생성자를 명시적으로 선언하므로써 복사 생성자가 자동으로 생성되는 것을 방지 ~unique_ptr() { delete ptr; } T* operator->() const {return ptr;} T operator*() const {return *ptr;} T operator[](int index) const {return ptr[index];} void reset() { //할당된 메모리 해제 delete ptr; } }; /* l-value r-value (left) (right) (int&) (int&&) 두번 거슬러 올라가면 원래값 int a = 10; int& Ra = a; //l-value a를 가르키는 참조자(Refference, 별명) Ra */ template<class T> class shared_ptr { private: T* ptr; int* refCount; template<class U> friend class week_ptr; public: shared_ptr() : ptr(NULL), refCount(NULL) {}; //클래스 내부 맴버로 사용되기 위해 explicit shared_ptr(T* p) : ptr(p), refCount(new int(1)) {}; shared_ptr(const shared_ptr<T>& origin) : ptr(origin.ptr), refCount(origin.refCount) { //복사 생성자 -얕은 복사 //복사 생성자에 explicit을 붙이면 A b = a가 A b(a)로 변환되는 것을 막음 (*refCount)++; } ~shared_ptr() { if(!--(*refCount)) { delete ptr; delete refCount; } } T* operator->() const {return ptr;} T operator*() const {return *ptr;} shared_ptr<T>& operator=(const shared_ptr<T>& origin) { //복사 대입 연산자(초기화 이후 새로 대입할 때), (*refCount)++ 해야하니 if(refCount-1 > 0) { ptr = origin.ptr; refCount = origin.refCount; (*refCount)++; return *this; } } T operator[](int index) const {return ptr[index];} int use_count() const { return *refCount; } }; template<class T> class week_ptr { private: T* ptr; int* refCount; public: explicit week_ptr(const shared_ptr<T> & origin) : ptr(origin.ptr), refCount(origin.refCount) {} explicit week_ptr(const week_ptr<T> & origin) : ptr(origin.ptr), refCount(origin.refCount) {} T* operator->() const {return ptr;} T operator*() const {return *ptr;} T operator[](int index) const {return ptr[index];} int use_count() const { return *refCount; } }; #endif
참고:
https://min-zero.tistory.com/entry/C-STL-1-3-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%8A%A4%EB%A7%88%ED%8A%B8-%ED%8F%AC%EC%9D%B8%ED%84%B0smart-pointer
https://uncertainty-momo.tistory.com/48
https://8bitscoding.github.io/cpp-im-move-make/
https://colinch4.github.io/2020-01-01/16_%EC%9D%B4%EB%8F%99-%EC%83%9D%EC%84%B1%EC%9E%90-%EB%B0%8F-%EC%9D%B4%EB%8F%99-%EB%8C%80%EC%9E%85-%EC%97%B0%EC%82%B0%EC%9E%90/
https://pyoungon.tistory.com/117구현을 하면서 새롭게 알게된게 몇개 있다. 그동안 private로 선언하면 그 안에서만 접근할 수 있다고 생각을 했었는데, 같은 클래스이기만 하면, 다른 인스턴스라도 접근할 수 있었다. 한 shared_ptr이 소멸해도, 주소값과 레퍼런스 카운트를 남기기 위해서는 동적할당을 사용하고, 복사가 될 때 포인터 주소를 넘겨주어야 한다. public으로 하자니 주소값이 공개되어 위험하고, private로 선언해야되는데 어떻게 전달할지 고민했었는데, 이번에 접근자에 대한 오해를 풀게되었다.
템플릿 내에서 다른 클래스 friend 선언하는 것도 자꾸 에러 메세지가 떠서 골치아팠다. 쓰지는 않지만 내부에 다른 템플릿을 또 선언하니 제대로 컴파일 되는 것으로 보아, week_ptr도 일반화되어있으니 컴파일러가 week_ptr에 해당하는 자료형이 있다는 것을 인지 시켜야 하는 것 같다.
클래스의 맴버로 스마트 포인터가 선언되는 경우나 함수의 인수로 스마트 포인터를 받는 경우때문에 호출될 기본 생성자와 이후 값을 할당할 복사 대입 연산자가 필요하다. 또 이동 생성자가 정의되면 기본 복사 생성자가 생성되지 않는 등의 규칙을 몰라 해매기도 하였다.(이동 생성자가 정의되면 기본 복사 생성자가 정의되지 않는 것은 최적화 때문이다.)
https://learn.microsoft.com/ko-kr/cpp/cpp/explicitly-defaulted-and-deleted-functions?view=msvc-170
예제
#include "src/SmartPtr.h" //스마트 포인터 테스트를 위해 대충 만든 List, 나중에 필요하면 템플릿으로 vector, List, stack, que 구현 해보기 class List { public: shared_ptr<List> pri; shared_ptr<List> next; int data; explicit List(int Data) { data = Data; }; }; void connect(shared_ptr<List>& current, shared_ptr<List>& NextNode) { //복사 대입 연산자 호출 current->next = NextNode; NextNode->pri = current; }; void setup() { Serial.begin(9600); Serial.println("***************"); Serial.println("unique_ptr"); unique_ptr<int> uPtr(new int(1)); Serial.print("Value of uPtr: "); Serial.println(*uPtr); auto uPtr2(move(uPtr)); //소유권 이전, auto로 해두면 컴파일러가 자료형을 찾아줌 Serial.print("Value of uPtr2: "); Serial.println(*uPtr2); /* 오류 발생 unique_ptr<int> uPtr3 = uPtr2; */ //메모리를 효율적으로 사용하기 위해 소멸자가 호출되기 전 메모리 삭제 uPtr.reset(); uPtr2.reset(); Serial.println("***************"); Serial.println("Arr Test"); unique_ptr<int> arr(new int[5]{5,4,3,2,1}); for(int i=0; i<5; i++) { Serial.print(arr[i]); Serial.print(", "); } Serial.println(""); Serial.println("***************"); Serial.println("shared_ptr"); shared_ptr<int> sPtr(new int(2)); Serial.print("Value of sPtr: "); Serial.println(*sPtr); Serial.print("Refference: "); Serial.println(sPtr.use_count()); Serial.println(""); for(int i=0; i<1; i++) { //복사 생성자를 통한 복사 auto sPtr2(sPtr); Serial.print("Value of sPtr2: "); Serial.println(*sPtr2); Serial.print("Refference: "); Serial.println(sPtr2.use_count()); Serial.println(""); //대입 연산자를 통한 복사, auto로 해도됨(복사 생성자 호출, 암시적 변환) shared_ptr<int> sPtr3 = sPtr; Serial.print("Value or sPtr3: "); Serial.println(*sPtr3); Serial.print("Refference: "); Serial.println(sPtr3.use_count()); Serial.println(""); } Serial.print("Refference: "); Serial.println(sPtr.use_count()); Serial.println("***************"); //week ptr, auto로 하면 shared_ptr로 선언되어 버리니 조심 Serial.println("week_ptr"); week_ptr<int> wPtr(sPtr); //share_ptr로부터 생성 Serial.print("Value of wPtr: "); Serial.println(*wPtr); Serial.print("Refference: "); Serial.println(wPtr.use_count()); Serial.println(""); week_ptr<int> wPtr2(wPtr); //week_ptr로부터 생성 Serial.print("Value of wPtr2: "); Serial.println(*wPtr2); Serial.print("Refference: "); Serial.println(wPtr2.use_count()); Serial.println("***************"); Serial.println("Member of class test"); shared_ptr<List> head(new List(0)); shared_ptr<List> A(new List(1)); shared_ptr<List> B(new List(2)); shared_ptr<List> C(new List(3)); connect(head,A); connect(A,B); connect(B,C); shared_ptr<List> temp = head; for(int i=0; i<2; i++) { temp = temp->next; } Serial.print("Value of List: "); Serial.println(temp->data); Serial.println("***************"); } void loop() {}
2023.2.11
소멸자에 삭제가 일어나면 "delete"를 출력하도록 하였을 때 한번만 출력된다.(sPtr만 해제됨) 배열 부분과 List 부분에서 제대로 해제가 안되고 메모리 누수가 일어난다. 배열인 경우 delete [] 를 수행하여야 하고, 복사 대입 연산자가 수행될 때는 기본생성자가 호출되어 null 인경우에는 문제없지만, 기존에 가르키는 대상이 있는 상태에서 가르키는 대상을 바꿀때는 기존 객체의 레퍼런스 카운트가 감소되지 않는 문제가 있다. 아마도 복사 생성자를 explicit으로 선언하고, 대입 연산자 재정의를 템플릿으로 일반화, delete와 nullptr로 바꾸는 것을 담당할 dispose 함수 등이 필요할 것 같다.
(2월 말 쯤 시간이 나면 좀 더 연구후 수정하겠습니다.)
'아두이노' 카테고리의 다른 글
아두이노 로터리 인코더로 UI 컨트롤 하기 (1) 2023.02.05 아두이노 오실로스코프 (0) 2023.01.31 아두이노 오르골만들기 (0) 2023.01.30