Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C++ & C#] 3일차 프로그래밍 & 지식 #27

Open
ggjae opened this issue Nov 9, 2021 · 3 comments
Open

[C++ & C#] 3일차 프로그래밍 & 지식 #27

ggjae opened this issue Nov 9, 2021 · 3 comments

Comments

@ggjae
Copy link
Owner

ggjae commented Nov 9, 2021

CPP 생성자에서 콜론은 왜 쓰는거야? (초기화 리스트)

https://hashcode.co.kr/questions/629/%EC%83%9D%EC%84%B1%EC%9E%90%EC%97%90%EC%84%9C-%EC%BD%9C%EB%A1%A0%EC%9D%80-%EC%99%9C-%EC%93%B0%EB%8A%94-%EA%B1%B4%EA%B0%80%EC%9A%94

https://treeroad.tistory.com/entry/%EC%83%9D%EC%84%B1%EC%9E%90%EC%97%90-%EC%BD%9C%EB%A1%A0-%EB%A5%BC-%EC%93%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0

  • 클래스가 레퍼런스를 멤버로 가질 때
  • non static const멤버가 있을 때
  • default 생성자가 없을 때
  • base class 초기화할 때

이럴 때, 초기화 리스트를 사용합니다.

default 생성자

http://tcpschool.com/cpp/cpp_conDestructor_defaultConstructor

깊은 복사, 얕은 복사 그게 뭐야?

https://blueshw.github.io/2016/01/20/shallow-copy-deep-copy/

얕은 복사는 해당 객체만 복사하여 새 객체를 생성하며, 복사된 객체의 인스턴스 변수는 원본 객체의 인스턴스 변수와 같은 메모리 주소를 참조한다

깊은 복사는 객체를 복사할 때 해당 객체와 인스턴스 변수까지 복사하여, 전부를 복사하여 새 주소를 담기 때문에 참조를 공유하지 않는다.

Listener패턴과 Observer패턴, 차이가 있을까?

그게 그거인 것 같은데.. 리스너가 곧 옵저버이다.

void (Dialog::*handler)(); // 범용적인 함수 포인터 하나 있으면 편할텐데..
    Dialog* target; // 멤버함수 넘기려면 객체가 있어야지..
public:
    void setHandler( void(*f)() ) { handler = f;}

이부분이 좀 어려운데, 함수 포인터에 대한 개념이 완벽해야 할 듯..
인턴첨부자료 폴더에 따로 넣어놓았으니 나중에 확인할 것

template<typename T>
class MemberAction : public IAction
{
    typedef void(T::*FP)();

    FP handler;
    T* target;
public:
    MemberAction(FP f, T* obj) : handler(f), target(obj) {}

    virtual void Execute() { (target->*handler)();}
};

또한 이해하면 좋을 듯

//--------------------------------------
template<typename T> void square(T a) {return a*a;}
square<int>(3);
square(3);

list<int> s(10,3);

함수 템플릿은 컴파일러가 함수 인자를 통해 타입을 추론합니다.
클래스템플릿을 생성하는 함수 템플릿을 도움 함수를 만들면 편리하다.

일반 함수 포인터와 멤버 함수 포인터는 절대 같은 타입에 담을 수 없다.

일반함수포인터와 멤버함수포인터를 바로 사용하지 말고, 클래스를 만들자. Action이라는 클래스를 만들어 일반 함수 포인터를 가지게 하고, MemberAction으로 멤버함수 포인터를 사용하는 클래스를 만들자.
이 둘을 묶어서 사용하려고 하면, 공통의 기반 Action클래스가 있으면 좋겠네?
IAction포인터는 둘 다 담을 수 있겠지, execute() = 0 라는 순수가상함수 만들고,

함수에 대한 정보는 인터페이스에 넣어놓아야 함.
객체만 선택해서 안에서 어떤 메뉴를 선택했는지 조사하여야 함.

Menu Event#2부터 리뷰
cpp에선 hpp로 헤더플플도 있다.. :)
command에서는, 메뉴가 눌려졌다는 사실을 알려주기만 하면 되고 커맨드는 눌린거만 생각해야지.
변하는 부분은 가상함수로 뽑아냈었잖아. 어떤 일을 직접 하지 말고 함수를 부르는거지.
=> 메뉴 이벤트를 처리할 때 가장 널리 사용되는 방법은?
파생클래스에서가상함수 재정의 => 너무 많이 써먹어야해..
변하는 것을 다른 클래스로 빼먹으면 Strategy패턴인데, 교체 가능해서 Interface가 필요하겠지.
따로 인터페이스를 짜서 메뉴 메세지를 처리하는거야.

menu_event7.cpp

함수 타입

C++11부터는, function<void()> f;
처럼 일반화된 함수 포인터 역할을 지원하는 템플릿이 생겼다. 헤더는 functional을 이용하여야 한다.
리턴 값은 보이드고, 파라미터가 없는것을 위처럼 나타내고
있는것을 나타내려면 function<void(int)> 식으로 사용하면 된다. 이것은 함수 포인터가 아닌 함수 타입이라고 생각하면 된다. 장점은, 멤버함수도 받을 수 있고 멤버함수는 실행하려면 객체가 있어야 하는데, bind를 이용해서 f = bind(&Dialog::Close, &dlg);`코드로 사용할 수 있다. 그 후 f();로 실행할 수 있다.
f = bind(&goo, 5); 등으로 인자를 고정해버릴 수 있다. bind는 인자 고정 가능. 표준이라서 기능이 상당히 많다.

일반함수, 멤버함수, 람다표현식, 함수객체 전부 담을 수 있다.

디자인관점때문에 본 것이고, 실제 활용시 function을 사용하는게 좋다. c++11을 사용하지 않는 환경이라면 함수포인터로 만들어 사용해야 한다.

  • 우리가 기존 클래스에 새로운 기능 추가를 할 때, 가장 간단한 방법은 '상속'이다.
  • 상속의 기본 개념은 기본 클래스의 기능을 물려받으면서 내가 원하는 기능을 추가시킬 수 있다는 것인데,
    하지만 객체가 아닌 클래스에 기능을 추가하는 것으로 실행 시간이 아닌 코드 작성시에 기능이 추가된다.

상속말고, 다른 방법 없을까?

구성을 통한 기능의 추가

구성을 이용해서 기존 기능에서 더 추가해보자.

class LeftMissile
{
    SpaceCraft* craft;
public:
    LeftMissile( SpaceCraft* p) : craft(p) {}

    void Fire()
    {
        craft->Fire(); // 기존 기능 수행.
        cout << "Left Missile : >>>>>>>>" << endl;
    }
};

클래스가 아닌 객체에 기능을 추가한 것.
컴파일 시간이 아닌 실행 시간에 기능을 추가한 것.

상속은 클래스에 코드 작성시 기능 추가가 가능하고, 구성은 객체에 실행 시간에 기능 추가가 가능하다.
상속은 경직되고, 유연성이 부족하지만, 구성은 유연성이 뛰어나다.
기능 추가 된 객체에, 다시 기능 추가하려면? 기반클래스가 있어야겠지..
우주선과 기능추가 객체는 동일한 기반 클래스를 가져야 한다.

Decorator

IDecorator라는 기능추가에 대한 공통 기반 클래스를 만들어서, 이 기반클래스를 이용합니다.

decorator5.cpp

의도 : 객체에 동적으로 새로운 서비스를 추가할 수 있게 한다.
기능 추가를 위해 서브 클래스를 사용하는 것보다 융통성 있는 방법을 제공한다.

@ggjae ggjae changed the title [C++ & C#] 3일차 프로그래밍 [C++ & C#] 3일차 프로그래밍 & 지식 Nov 9, 2021
@ggjae
Copy link
Owner Author

ggjae commented Nov 9, 2021

Adapter 패턴. 그게 뭐야?

도형편집기에서 Text를 추가하려면, Shape로부터 파생되어야 하고, Draw()함수에서 글자를 출력해야 한다.
텍스트뷰라는 헤더가 있었고, 그것을 도형편집기에서 사용하기 위해 인터페이스를 변경하여 다중 상속 처리한다.
class Text : public TextView, public Shape ....
안에서 인터페이스를 변경해주고, 우리가 적용하기 위해서 이런 코딩방식을 진행했는데 이게 곧 어댑터 패턴이다.

한 클래스의 인터페이스를 클라이언트가 사용하고자 하는 다른 인터페이스로 변환한다. 호환성때문에 사용할 수 없었던 클래스들을 연결해서 사용한다.

어답터에 종류에는 클래스 어답터 / 객체 어답터가 있다.

클래스 어댑터, 객체 어댑터

클래스 어댑터

TextView 클래스의 인터페이스를 도형편집기에 맞도록 수정

근데 만약, tv를 넣고 싶다면? 이것은 클래스의 변환이 아닌, 객체 형체의 어댑터가 필요합니다..

ObjectAdapter : public TextView, public Shape가 아닌, public Shape만 받고 안에 TextView의 포인터를 넣는게 핵심입니다.
포인터나 참조로 넣게되면 만드는게 아니라 가리키기만 할거니까, 문자열을 받는게 아니라 이미 존재하는 텍스트만 받는거죠. 피뷰에다 p를 주고.. pView에 대해서 show()하는거죠.

기존에 있던 객체를 변경할 순 없고, 새로운 객체를 만들 때 써야죠. 기존에 객체가 존재했던 인터페이스를 교체하고 싶은거라면 객체 어댑터를 써야겠죠.

클래스 어댑터는 클래스의 인터페이스 변경으로, 다중 상속 또는 값으로 포함되는 경우가 많다.
이미 존재하던 객체의 인터페이스는 변경할 수 없다.

객체 어댑터는 객체의 인터페이스를 변경한다. 구성을 사용하는 경우가 많아서 기존 객체를 포인터나 참조로 포함합니다.
이미 존재하는 객체가 아니라 포인터나 참조로 받아야 함.

@ggjae
Copy link
Owner Author

ggjae commented Nov 9, 2021

어댑터패턴의 실전 STL)
// stack을 만들어보자.

int main(){

}

링크드리스트를 한쪽으로만 쓰면 스택이 되잖아요. 함수 이름만 스택에 맞게 바꿔주면 되겠죠?
리스트의 함수 이름을 stack처럼 보이도록 변경해봅시다. 그러면 stack이 될 것 같아요.
template class stack : public list
{
public:
void push(const T& a) { list::push_back(a);} 등...
리스트의 팝, 푸시만 호출하면 되죠.
확인해볼 수 있어야하니까... list에서 파생된 기본 stack에 s.push_front(20);같이 앞에다 넣어버리면 어떡해요?
기반클래스의 함수들을 얘네가 다 쓸 수 있잖아요. list의 함수들을 전부 private 상속을 받는 방법이 있는데.
모든 리스트의 멤버함수는 private으로 상속되어 막을 수 있겠죠.

두번째 방법은, list를 stack의 멤버변수로 놔두면 되잖아요! 그러면, 리스트를 참조로 불러서 써먹으면 되니까. 훨씬 깔끔하겠죠.

여기서 알 수 있는게 상속을 이용하는 어댑터 / 포함을 시키는 어댑터 차이점을 알면 좋겠죠.
vector로 가도 문제 없을까요..? 사용자가 바꿀 수 있는 권한을 주는게 더 좋을거 아니에요.
typename 추가해서 리스트를 기반으로 하던 벡터를 기반으로 하던... 하면 될거 아니에요?
스택 어댑터.

Container Adapter -> Sequence Container의 인터페이스를 수정해, stack queue, priority_queue등 을 제공한다.
어댑터패턴을 잘 활용해서 만든것이 STL의 스택과 큐이다.

iterator Adapter
이터레이터 어댑터, C++11은 람다표현식이 가능하니까 for_each(p1,p2,[](int a) {cout << a << endl;})이 가능.
for_each는 계속 더할텐데, 뒤쪽으로 가면 안된다. 반복자는 반복자인데, ++을 하면 원래 동작의 반대로 하는 걸 구현해보자. ++을 하면 뒤로 오게 만들자. 거꾸로 동작하는 ++이 있으면 되는데 reverse iterator가 있다. reverse_iterator<?> p3(p2); -- p2로 초기화된다. p3는 4를 가리키고, p4를 만들 때 p1으로 초기화하면 되겠다.

reverse_iterator<?> p3(p2); // --p2로 초기화
reverse_iterator<?> p4(p1); // --p1으로 초기화
cout << *p3 << endl;
++p3;
cout << *p3 << endl;

하면, 4, 3이 찍히죠. auto를 자주 쓰다 보면 타입을 잊어버리는데, list::iterator를 사용하면 된다.

reverse_iterator

기존 반복자의 동작을 거꾸로 하는 어댑터
다양한 알고리즘을 역순으로 적용할 수 있다.

proxy 패턴

어떤 객체에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴
프로세스간 통신을 기반으로하는 C-S 프로그램 모델
계산기 서버, 클라이언트 (둘 다 독립된 프로세스로 동작)

Proxy 는 원격지의 서버를 대신 하는 클래스.
프록시의 장점은 명령 코드 대신 함수 호출 사용. - 잘못된 명령 코드를 사용하지 않게 됨.
또한, Client는 IPC에 대해서 알 필요 없잖아.
보안의 기능을 추가하거나, 자주 사용되는 요청에 대한 캐시를 추가할 수 있다.

Proxy <-> Stub
proxy는 함수 호출을 명령 코드로 변경해서 서버에 전달

client4.cpp
프록시는 stub와 통신한다.

서버클래스와 프록시 클래스 동일한 이름이 있으면 안된다.
동적 모듈 load ( void* addr = ec_load_module("calcproxy.dll");
F f = (F)ec_get_function_address(addr, "CreateCalc");
ICalc* pCalc = f();

client와 동적 모듈 사이의 약속
client는 동적 모듈에 있는 클래스의 이름을 알 수 없다. 동적 모듈안에 있는 클래스의 객체를 생성하기 위해 약속된 함수가 필요하다.

Proxy 객체의 수명관리
동적 모듈에서 생성한 객체는 동적 모듈에서 파괴하는것이 좋다...

인터페이스와 참조계수
참조계수를 사용하려면 ICalc 안에도 참조계수 함수가 있어야 합니다..

오픈소스들은 스마트포인터를 제공해주는 이유가, addref나 release함수를 자동으로 호출하기 위한거다.
프록시외에도 동적모듈로 제공하는 기법들이 많고, client6.cpp같은 경우 오픈소스와 느낌이 비슷하니 잘 이해할 것

프록시패턴에 대한 정리
대신하는 걸 만드는걸 다 프록시라고 하는데, proxy에도 패턴 종류가 되게 많아요. remote proxy, virtual proxy, protected proxy 등.... 생성하기 힘든 자원에 대한 imageproxy(용량이커서),


facade(퍼사드) 패턴

TCP 서버를 만드는것은
network 라이브러리 초기화 - 소켓 생성 - 주소 지정 - 대기상태로 변경 - 접속을 대기 - 접속된 클라이언트와 통신 - 소켓 닫기 - 네트워크 라이브러리 클린업

소켓 관련함수를 클래스화하자.
하나의 클래스에 모든 기능을 넣는게 제일 편하겠죠. ( X )
기능별로 분리해서 각각 클래스로 설계한다. ( O )

N/W, 라이브러리 초기화 클린업 - NetworkInit
IP 주소 관리 - IPAddress 클래스
socket프로그래밍의 함수 - Socket클래스

클래스 설계...
장점 : 세부적인 코드가 클래스 내부에서 처리되므로 함수 기간의 코드보다 간결하고 사용하기 쉽다.
=> 좀 더 사용하기 쉽고 간결한 형태로 제공할 수 없을까?

퍼사드는 서브 시스템을 합성하는 다수의 객체들이 인터페이스 집합에 대해 일관된 하나의 인터페이스를 제공할 수 있게 한다.
퍼사드는 서브시스템을 사용하기 쉽게 하기 위한 포괄적 개념의 인터페이스를 제공한다.

약간 SDK개발이 퍼사드패턴을 되게 많이 이용하지 않을까 싶다... :)

@ggjae
Copy link
Owner Author

ggjae commented Nov 9, 2021

브릿지 패턴

새로운 MP3가 나오면, 확장성이 있게 만드려면 제품을 만들 때 그냥 만들면 안되고, 인터페이스를 먼저 설계해야죠.
모든 MP3 제품은 play, stop 함수를 순수가상함수를 통해서 제작하고 가상소멸자까지 필요하겠죠.

모든 MP3제품을 만들 땐, MP3부터 파생되는것.

PIMPL( Pointer to IMPlementation )의 장점
컴파일 속도를 향상 시킨다. 또한 완벽한 정보 은닉이 가능하다. - 헤더파일(pointlmpl.h)를 감출 수 있다.
이게 곧 구현부와 인터페이스를 분리하는 브릿지패턴과 비슷함. 어우 이 부분은 좀 어렵습니다...


뒤에 있는걸 사용하기 위한 껍질.. 즉 간접층을 도입.
해결하기 어려워 보이는 문제도 간접층을 도입하면 해결할 수 있다.
의도가 인터페이스를 변경해서 사용자에게 다른것처럼 보이게 해주는 것.
왜 간접층을 사용하는가? 의도가 중요.
인터페이스의 변경은 Adapter, 대행자는 Proxy, 서브시스템의 복잡한 과정을 감추기 위해 Pacade, Update를 독립적으로 수행하기 위해 Bridge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant