2014-03-12 2 views
29

Boost.Asio를 사용하여 C++ 11에서 서버 응용 프로그램을 만들고 있습니다. 저는 새로운 연결을 수락하는 클래스를 만들었습니다. Server. 그것은 기본적으로 그냥 : 나는 두 가지 방법을 발견했습니다콜백의 장단점 (std :: function/std :: bind) vs 인터페이스 (추상 클래스)

void Server::Accept() { 
    socket_.reset(new boost::asio::ip::tcp::socket(*io_service_)); 
    acceptor_.async_accept(*socket_, 
         boost::bind(&Server::HandleAccept, this, boost::asio::placeholders::error)); 
} 

void Server::HandleAccept(const boost::system::error_code& error) { 
    if (!error) { 
    // TODO 
    } else { 
    TRACE_ERROR("Server::HandleAccept: Error!"); 
    } 
    Accept(); 
} 

가 가야 어디든지 소켓을 이동하는 "수정"할 수있는 TODO 주석, 즉 (I보다가 확신). 내 경우에는 Server 인스턴스를 소유 한 클래스 인스턴스로 다시 전달하기 만하면됩니다 (이 인스턴스는 Connection 클래스로 래핑 된 후 목록에 삽입됩니다). HandleAccept에서라고 std::function<void(socket)> OnAccept :

  1. Server는 생성자의 매개 변수가 있습니다.
  2. 추상 클래스 인 IServerHandler을 만들거나 하나의 가상 메서드 OnAccept을 만듭니다. Server은 해당 생성자에서 매개 변수로 IServerHandler을 가져 오며 서버 인스턴스를 소유하는 클래스 인스턴스는 IServerHandler을 확장하고 Server을 구성하고 *this을 매개 변수로 사용합니다.

옵션 1과 옵션 2의 장단점은 무엇입니까? 더 좋은 옵션이 있습니까? 내 Connection 클래스 (OnConnectionClosed)에서 동일한 문제가 발생합니다. 또한 시스템을 설계하는 방법에 따라 OnPacketReceivedOnPacketSent 콜백이 필요할 수 있습니다.

+0

평생을 어떻게 관리하고 있습니까? 나는'weak_ptr >'이 좋은 콜백을 만든다는 것을 발견했다. (호출자는'shared_ptr'을 소유하고'.reset()'에 의해 그것을 제거한다). – Yakk

+0

'std :: function' 콜백을 변수로 사용하여'1'을 선호합니다. 그런 식으로'if (onData) onData (ptr, sz)'를 호출하기 전에 나중에 설정할 수 있습니다. – stefanB

+1

하나의 특별한 경우 객체가 움직일 때 상속이 바람직합니다. 옮겨진 객체를 캡쳐 한 람다는 무효가되어 이동 생성자/할당은 목적지 객체에 대한 새로운 람다를 생성해야합니다. 상속은'this' 포인터의 유효성을 유지합니다. – DanielKO

답변

41

난 강력하게 몇 가지 이유에 대한 첫 번째 방법을 선호 : 인터페이스/클래스 계층 구조를 통해 개념/기능을 대표

  • 은 미래의 코드베이스가 적은 일반 유연하고 더 어렵게 mantain에 또는 규모한다 . 이러한 종류의 디자인은 유형 (필수 기능을 구현하는 유형)에 대한 요구 사항을 부과하여 향후 수정이 어려우며 시스템이 변경 될 때 가장 실패하는 경향이 있습니다 (기본 클래스가 이 유형의 디자인).

  • 콜백 접근 방식은 오리 입력의 전형적인 예입니다. 서버 클래스는 필요한 기능을 구현하는 호출 가능 객체 만 필요합니다 ().. 아니 "귀하의 유형이이 계층에 연결되어야합니다" 조건이 필요하므로처리를 구현하는 유형은 완전히 무료입니다. 내가 말했듯이

  • 또한, 서버는호출 가능한 일을 기대 : 그것은 예상되는 함수 서명 무엇이든 될 수있다. 이것은 핸들러를 구현할 때 사용자에게 더 많은 자유를줍니다.

    • 거의 모든 표준 라이브러리 알고리즘 반복기 범위에 기초 : 전역 함수 바운드 멤버 함수, 펑 등

    예를 들어 표준 라이브러리를 가지고있을 수있다. C++에 iterator 인터페이스가 없습니다..반복자는 반복자의 동작을 구현하는 모든 유형입니다 (역 참조, 비교 가능 등). 반복자 유형은 완전히 자유 롭고 구별되며 분리되어 있습니다 (주어진 클래스 계층에 대해 잠그지 않음).

  • 또 다른 예는 비교기 일 수 있습니다. 비교기 란 무엇입니까? 부울 비교 함수의 서명이있는 것은 무엇이든간에 두 개의 매개 변수를 사용하고 두 입력 값이 특정 (예 : 비교 기준. Comparable 인터페이스가 없습니다.

+0

+1 그리고 나는'onData','onConnect','onDisconnect'와 같이이 콜백을 인스턴스 변수로 갖는 것을 선호합니다. 필요한 것이 무엇인지에 따라 달라지기 쉽고, 호출하기 전에'if (onData) onData (ptr, sz)'... 쉽게 – stefanB

+0

@stefanB 나도, 나는 보통 정책 기반 디자인으로 일한다. – Manu343726

+4

동전에는 양면이 있습니다.이 대답은 * 호출 가능한 * 접근 방식의 장점에 초점을 둡니다.반면 콜백을 사용하는 복잡한 시스템은 프로그램의 실행 흐름이 코드에서 명확하지 않기 때문에 추론하고 유지하기가 훨씬 더 어렵습니다. 각 접근법은 복잡한 시스템에서 그 이점과 위치를 가지고 있습니다. –

0

어떤 부스트 버전을 사용하고 있습니까? IMHO가 coroutines를 사용하는 가장 좋은 방법. 코드는 다음과 같이됩니다. 그것은 동기 코드처럼 보일 것이지만 지금은 모바일 장치에서 쓰고 있기 때문에 비교할 수는 없습니다.

1

많은 경우에 특정 유형에 PREFER 바인딩을 사용한다는 것을 언급하기 만하면됩니다.
따라서 클래스에 IServerHandler이 있어야한다고 선언하면 개발자와 다른 개발자가 클래스 작업을 위해 구현해야하는 인터페이스를 이해하는 데 도움이됩니다.
향후 개발에서 IServerHandler에 더 많은 기능을 추가하면 클라이언트 (예 : 파생 클래스)가 개발을 계속하도록 강요합니다.
원하는 동작 일 수 있습니다.

+0

크고 큰 시스템을 구축 할 때 각 개체의 다른 인터페이스에 대한 종속성은 매우 중요하며 시스템을 긴밀하고 일관성 있고 안전하게 유지하는 데 도움이됩니다. 객체 캡슐화없이 '함수에 대한 포인터'로 돌아가는 것은 때로는 너무 많은 자유입니다. –

+1

하지만 클라이언트의 실제 유형에는 궁극적으로 관심이 없습니다. 단지 호출 할 수있는 정의 된 서명을 준수하는 것입니다. 권리? –

+0

거의 오른쪽 ... –

1

모든 것이 당신의 의도에 달려 있습니다.

한편, 은 특정 기능이 특정 유형에 속한다고 가정하면 가상 기능 또는 멤버 포인터와 같은 계층 구조로 구현해야합니다. 이 의미는 코드를 올바르게 사용하기 쉽고 잘못 사용하기 어렵 기 때문에 유용합니다.

반면에 특정 기본 클래스와 밀접하게 결합 된 부담을 겪지 않고도 일부 추상 "기능을 수행하고 여기로"기능을 원하면 명확하게 다른 것이 더 적합 할 것입니다 자유 함수에 대한 포인터 또는 std :: 함수 등

소프트웨어의 특정 부분의 특정 디자인에 더 적합합니다.

관련 문제