전체 글 65개
Post
UniRx는 유니티에서 캐릭터가 목표지점에 도착했을때를 알리기위해서 어떻게 해야하는지를 검색해보다가 접하게된 라이브러리입니다.
UniRx는 .NET Reactive Extensions를 유니티에 맞게 다시 구현한 것으로,
여기서 RX는 비동기 프로그래밍과 Observable 시퀀스를 이용해 이벤트를 처리하기위한 라이브러리입니다.
RX(ReactiveX)
: observer 패턴 + iterator 패턴 + functional 프로그래밍 의 조합이라고하는데
ㅎㅎㅎㅎ 정말 말만 봐도 어렵네요! 더 자세하게 봅시다!
observer패턴은
- 관찰차패턴으로써 특정 이벤트가 일어나는것을 감시
iterator 패턴은
- 집합체안에 들어있는 모든 항목에 접근할 수 있게 해 주는 방법을 제공
- 집합체 내에서 어떤 식으로 일이 처리되는지 몰라도 그 안에 들어있는 항목들에 대해서 반복작업을 수행
functional 프로그래밍
보통 우리가 하는 프로그래밍은 "명령형"프로그래밍으로써
함수를 너는 이렇게하고, 너는 저거하고 이거랑 저거 섞어서 이런 결과물을 내놓아라!라고 한다면..
<기계에 카카오 100kg을 넣고 그 다음 분쇄기계에서 갈고 그 다음에는 .... 마지막에 초콜릿을 예쁜모양으로 만들어낸다>
함수형 프로그래밍은 "선언형"프로그래밍이라서
함수를 이것은 이거다 라는 "행위가 아닌" "값"으로 보고 프로그래밍을 하는느낌입니다.
<기계에 카카오 100kg양을넣고 그만큼의 초콜릿을 만들어낸다(방법을 적지않고 목표만 명시)>
이렇게 얘기하면 잘 감이 안올 수 있는데...코드를 통해 비교해보면 좀 더 쉽게 이해가 될 수 있습니다.
//명령형으로 쓸 경우(비함수형 코드)
var filtered = [];
for (var i=0; i<students.length; i++)
{
if(stduents[i].division == "이과")
filtered.push(students[i]);
}
filtered = filtered.splice(0,3);
var result =""
for(var i=0;i<filtered.length;i++)
{
result += '${filtered[i].name}(${filtered[i].major})';
}
console.log(result);
//선언형으로 쓸 경우(함수형 코드)
consol.log(
_(students)
.filter((i) => i.division == '이과')
.take(3)
.map((i) => '${i.name}(${i.major})')
.reduce((sum,i) => sum+" " +i)
)
위 두 코드는 모두 학생들 중에서 이과인 3명의 학생의 이름과 전공을 출력하는 형태를 띄고있습니다.
함수형 언어나 프로그래밍을 위한 라이브러리들에는 컬렉션 내 요소들을 다양하게, 연속적으로 처리할 수 있는 다양한 도구들이 존재하고, for문을 돌려가며 직접 변수를 선언하고 하나하나 설계했던것들을 이제는 도구들의 작동과 사용법을 익혀놓았다가 적재적소에 갖다붙이기만하면 되는거죠!
이러한 프로그래밍을 한다면 장점으로써 아래와 같은것들이 있습니다.
- 우리가 흔히 쓰던 방식이 아닌 새로운 방식이기때문에 다양한 사고방식을 얻을 수 있음 (사고의 전환)
- 사용하는 모든 데이터가 변경 불가능(immutable)하고 함수는 부수 효과를 가지고 있지 않기 때문에 외부환경으로부터 독립적이므로 동시성과 관련된 문제를 원천적으로 차단
- 함수는 입력과 그에 대한 출력만을 책임지기 때문에(순수함수) 테스트가 쉽고 가독성이 좋아 깔끔하고 유지보수가 용이
(부수효과 : 한 변수의 값이 변할 시, 다른 변수의 값도 변하게되는 등의 의도하지않은 상태가 나타나는것)
함수형 프로그래밍에서 말이 조금 길어졌는데,이러한 코드문법을 이용해서 Async Event(비동기 이벤트) 와 Observer 디자인패턴의 통지 처리를 이용해 미리 이런 조건이 발생하면 이런 처리를 하라고 지시를 내려놓고 그 지시가 통과된 시점에서만 통지를 받는 형태인 Rx라이브러리를 이용하여서 위와같은 함수형프로그래밍을 거의 모든언어에서 적용할 수 있습니다. 문법도 다들 비슷하구요!
그리고 UniRx는 이런 Rx라이브러리를 유니티 C#에 최적화되어있습니다.
UniRx 사용법
유니티 에셋스토어 or 깃허브에서 다운이 가능합니다
UniRx가 쓰이면 좋은점
- 시간을 간단하게 취급한다
- GUI구현을 간단히 쓸 수 있다
UI 반응이나 로직 상에 특정 변수 값이 변경되는 순간의 처리
- event의 상위호환으로써 event에 비해 유연한기술을 사용할 수 있다
더블 클릭 판정이나 유저의 입력 시간 제한 등의 일정 시간동안 대기 및 체크 해야 하는 이벤트
기존 유니티 event의 준비단계나 sendMessage등의 통지구조보다 간단하다
- update문을 없앨 수 있다
작업별로 병렬로 늘어서 작성가능하다. (처리의도를 알기쉽다, 기능 추가/변경/제거가 용이)
복잡한 로직을 operator 조합으로 구현가능하다
자원관리가 탁월하다. ( Observe하는 Stream을 열어두기만하면 된다)
==> 종합적으로 보았을때, 작성을 간단하게 해주고 봤을때 이해하기쉽게 한다는 프로그래머가 개발코드를 짤때 편리함이 매우 높다라는 장점이 있습니다. 어떤 새로운기능! 신박한기능! 보다는 기존의 코드를 간결하게! 좀 더 쉽게! 구현이 가능!
자아 좋은점도 다 보고 어떤건지 간단하게 알아봤는데, 과연 나쁜점은 없을까요???
UniRx단점
- 학습 코스트가 높고 개념적으로도 어렵다
데이터 질의기능(연산자)인 링큐(LINQ)를 사용하기때문에 난이도가 높아진다.
흔히 쓰던 명령형프로그래밍이 아니기때문에 선언형 프로그래밍관점으로 보아야한다
- 도입하는 경우에는 프로그램의 설계부터 다시 생각할 필요가 생긴다
라이브러리가 아닌 언어확장이라고 생각해야한다.
- 스트림의 수명관리가 중요하다
스트림이 생성되면 스트림에 설정한 종료 조건을 모두 충족하기 전까지는 끝나지 않기때문에 퍼포먼스 저하나 에러에 의한 오동작이 일어날 수 있다
알아보자마자 끝난것같긴한데ㅎㅎ 글을 쓰면서 이해가 안가는 부분들, 적용할지말지 고민하면서 찾아본 좋은 사이트들을 밑에 링크걸어두겠습니다. :D 다들 너무 글을 잘쓰셨어요!!!
----------------------------------------참고-----------------------------------------
https://www.slideshare.net/agebreak/160402-unirx
위 링크는 개념적으로 이해하기가 정말 좋았습니다 :D
되게 제 눈높이에 맞춰서 잘 알려주셔서 가독성이 너무 좋고 술술 넘어갑니당
https://skuld2000.tistory.com/31
https://tech.lonpeach.com/2019/11/02/UniRx-Getting-Started-1/
https://rito15.github.io/posts/unity-study-unirx/
간단하게 필요한 설명만 딱딱 적어주셔서 읽기 좋았습니다
https://nidelva.tistory.com/27
이 분의 블로그는 당시에 직접 코드를 짤때 이해를 쉽게 해주었습니다!
https://www.youtube.com/watch?v=jVG5jvOzu9Y
함수형 프로그래밍에 대해서 이해를 정말 쉽고 간단하게 해주었습니다 ㅎㅎ 위 예시들도 영상에서 많이 참고했습니다
참고로, 저는 위 블로그에서 처음 발견을 하게되어 어떤것인지 알아보고 또 적용을 하게되었습니다. 코드에 대해서 설명도 적어주셨기에 당장 쓰는것에도 문제없을만큼 간단명료해보였는데 아무래도 제가 더 공부를 하고나서 적용시키는게 맞을것같습니다!
'STUDY > 유니티' 카테고리의 다른 글
[C#] null을 비교하기위한 여정2, is 와 FakeNull (0) | 2023.01.21 |
---|---|
[C#] null을 비교하기위한 여정1, ==와 Equals() (0) | 2023.01.18 |
[Unity] 메지카복셀에서 내가 만든 캐릭터 유니티로 불러오기 (0) | 2022.09.10 |
[Unity] C# 문자열보간 (0) | 2022.08.02 |
[텍스처 최적화]스프라이트 아틀라스 사용법 (0) | 2022.06.16 |
Post
다형성을 가진 기본 클래스에서 비가상 소멸자를 통해 소멸하면 어떻게 될까?
class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
...
};
TimeKeeper* getTimeKeeper();
TimeKeeper *ptk = getTimeKeeper();
...
delete ptk;
기본 클래스 포인터를 통해 파생 클래스 객체가 삭제될 때 비가상 소멸자가 들어 있으면, 대부분 그 객체의 파생 클래스 부분이 소멸되지않게 된다.
파생 클래스의 소멸자도 실행되지 않지만 기본클래스 부분은 소멸 과정이 제대로 끝나 '부분소멸'이 되게된다.
이 문제를 없애기위해서는 기본 클래스에 가상 소멸자를 넣어놓으면된다.
class TimeKeeper {
public:
TimeKeeper();
virtual ~TimeKeeper();
...
};
TimeKeeper* getTimeKeeper();
TimeKeeper *ptk = getTimeKeeper();
...
delete ptk;
소멸자 앞에 virtual 만 붙여줄 뿐인데, 파생 클래스 까지 객체 전부가 소멸된다.
하지만 기본클래스로 설계되지 않거나 다형성을 갖도록 설계되지않은 클래스조차 마구잡이로 가상소멸자를 선언하면 안된다.
class Point {
public:
Point (int xCoord, int yCoord);
~Point();
private:
int x,y;
};
위와같은 코드가 있을때, Point객체는 int가 2개 있으니 64bit를 생각하면 64bit레지스터에 맞게된다.
하지만 Point 클래스의 소멸자가 가상 소멸자라면 64bit가 아닌 96bit가 된다.(C++에서는 가상함수를 구현하기 위해 클래스에 별도로 가상 함수 테이블 포인터 라는 자료구조가 들어가야하기 때문이다.)
그리고 다른 언어로 선언된 동일한 자료구조와도 호환성을 잃게된다. 다른 언어에선 '가상 함수 테이블 포인터' 라는 vptr이 없기때문이다.
그렇기때문에 가상 소멸자를 선언하는것은 그 클래스에 가상 함수가 하나라도 있을 경우에만 선언하고, 가상 함수가 없을때에는 가상 소멸자를쓰지말자!
하지만 모든 기본 클래스가 다형성을 갖도록 설계된것은 아니다.
(표준 string타입, STL컨테이너타입)
그리고 기본클래스로는 쓰일 수 있지만, 다형성은 갖지않도록 설계된것도 있다.
(Uncopyable, 표준 라이브러리의 input_iterator_tag)
이런 클래스들은 파생 클래스 객체의 조작이 허용되지 앟기때문에 가상 소멸자를 볼 수 없다.
+) 순수 가상 소멸자를 이용하면 추상클래스를 쉽게 만들 수 있다.
우리가 아는 추상클래스는 보통 순수가상함수가 들어가 있다.
하지만 추상클래스이면 좋겠지만 넣을 순수 가상 함수가 없다면 굳이굳이 만들어서 넣을필요는 없고,
순수 가상 소멸자만 있더라도 추상클래스가 될 수 있다.
class AWOV {
public:
virtual ~AWOV() =0; //순수 가상 소멸자 선언
};
AWOV:: ~AWOV() {} //순수 가상 소멸자 정의
'STUDY > Effective C++' 카테고리의 다른 글
항목 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 (0) | 2022.08.17 |
---|---|
항목 8: 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2022.08.17 |
항목 6: 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자 (0) | 2022.06.23 |
항목 5: C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2022.06.23 |
항목 4: 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2022.06.23 |
Post
항목5를 보면 알 수 있다시피 컴파일러는 클래스에 대해 기본생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들 수 있다.
그러면 복사가 되지않게 복사 생성자 자체를 만들지 않을 수 없는걸까?
1. 해당 멤버함수를 private으로 선언
class HomeForSale {
public:
...
private:
...
HomeForSale(const HomeForSale&);
HomeForSale& operator=(const HomeForSale&);
};
결론은 컴파일러에서 자동으로 생성하는것을 방지하고자하는 멤버함수를 따로 private으로 선언만하고 구현을 하지않으면 되는일이다.
2. Uncopyable 상속
class Uncopyable {
protected:
Uncopyable() {}//생성과 소멸 허용
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&) //복사 방지
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale: private Uncopyable {
...
};
아니면 기본 클래스인 Uncopyable 을 상속받으면 되는일이다. 좀 더 깔끔하긴하다.
이 Uncopyable이라는 클래스를 보면 생성과 소멸을 허용하는건 protected내에서 선언을 해준다.
그리고 앞의 방법과 똑같이 방지가 되었으면 하는 멤버함수의 경우에는 private으로 선언을 한다.
3. noncopyable 상속
Uncopyable과 같은 기능을 하나, 부스트 라이브러리안에 있는 클래스이다.
'STUDY > Effective C++' 카테고리의 다른 글
항목 8: 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2022.08.17 |
---|---|
항목 7: 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2022.06.25 |
항목 5: C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2022.06.23 |
항목 4: 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2022.06.23 |
항목 3: 낌새만 보이면 const를 들이대 보자! (0) | 2022.06.18 |
Post
컴파일러는 우리를 대신해서 기본생성자, 복사생성장, 복사 대입 연산자, 소멸자 등 기본형 함수를 만들어낸다.
하지만 의 경우에는
std::string newDog("Persephone");
std::string oldDog("Satch");
NameObject<int> p(newDog, 2);
NameObject<int> s(oldDog, 36);
p=s;
참조자 p는 원래 자신이 참조하고 있는것과 다른 객체를 참조할 수 없다. 그렇다고 값 자체가 바뀌는것은 대입연산에 직접관여하지 않는 객체(string에 대한 포인터나 참조자를 품는 다른 객체들)까지 영향을 받게된다.
그렇게 된다면 컴파일 거부가 뜰 수 있다.
이럴 경우를 대비해서 복사 대입 연산자의 경우에는 직접 정의를 해야한다.
'STUDY > Effective C++' 카테고리의 다른 글
항목 7: 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2022.06.25 |
---|---|
항목 6: 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자 (0) | 2022.06.23 |
항목 4: 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2022.06.23 |
항목 3: 낌새만 보이면 const를 들이대 보자! (0) | 2022.06.18 |
항목2: #define을 쓰려거든 const, enum, inline을 떠올리자 (0) | 2022.06.18 |