2010-01-26 4 views
9

사용할 수있는 원격 프로세스와의 상호 작용을 처리하는 클래스에서 작업하고 있습니다. 실제로 대부분의 경우 그렇지 않을 것입니다. 그것이 아니라면, 그 계급의 대상은 삶에 아무런 목적이 없기에 사라져야합니다. 프로세스가 존재하지 않는 경우에는 예외를 던지는, 생성자에서생성자의 네트워크 연결 설정 : 좋든 나쁨?

  1. 핸들 연결 설정 :

    은 덜 추한 것입니다.
  2. 별도의 connect() 메서드로 연결 설정을 처리하고 프로세스가 없으면 오류 코드를 반환합니다.

옵션 1)에서 호출 코드는 당연히 해당 클래스의 인스턴스화와 try() 블록에서 다루는 모든 것을 인스턴스화해야합니다. 옵션 2에서는 단순히 실패한 경우 connect()의 반환 값을 확인하고 실패한 경우 객체를 반환 할 수 있지만 RAII 호환이 적습니다.

관련하여 옵션 1) std :: exception 클래스 중 하나를 던지거나, 나 자신의 예외 클래스를 파생 시키거나, 내 자신의 예외 클래스를 롤백하거나, 그냥 문자열을 던지려면? 나는 이들 중 첫 번째를 배제하는 것 같은 실패의 징후를 포함시키고 싶다.

명확하게 편집 됨 : 원격 프로세스가 동일한 시스템에 있으므로 ::connect() 호출이 차단되는 것은 거의 없습니다.

답변

6

차단 성격이 일반적으로 객체를 구성 할 것으로 예상되는 것이 아니기 때문에 차단을 수행하는 것이 좋지 않다고 생각합니다.connect()을 생성자로 사용합니다. 따라서이 기능을 사용하면 클래스 사용자가 혼란을 느낄 수 있습니다.

예외적으로 std :: exception에서 새 클래스를 파생시키는 것이 일반적으로 가장 좋습니다 (그러나 대부분의 작업). 이를 통해 포수는 catch (const myexception &e) {...} 문을 사용하여 특정 유형의 예외에 대한 작업을 수행 할 수 있으며 catch (const std::exception &e) {...} 인 모든 예외에 대해 한 가지 작업을 수행 할 수 있습니다.

관련 질문을 참조 : How much work should be done in a constructor?

+1

블록 생성자가 의미있는 RATI 기반 잠금을 사용하는 경우는 물론 예외가 있습니다. 물론 문서화와 적절한 명명이 중요합니다. –

+0

+1 별도의 연결 메소드에서 차단 작업을 수행하면 해당 작업을 취소하는 'cancel'메소드가 열려있는 상태로 두십시오. –

0

내가 생성자가 private 멤버를 초기화 이외의 다른 일을하지해야한다고 생각하기 때문에 내가 두 번째로 갈 것입니다. 그 외에도, 실패 (예 : 연결하지 않는 것)를 처리하는 것이 더 쉽습니다. 정확히 무엇을 할 것인가에 따라 개체를 유지하고 필요할 때 connect 메서드를 호출하여 다른 개체를 만들지 않아도됩니다.

예외 사항은 직접 만들어야합니다. 이렇게하면 호출자가 필요할 때 특정 롤백 작업을 수행 할 수 있습니다.

0

예상치 못한 잘못된 API 디자인을 차단하는 생성자와 연결하지 마십시오.

연결 방법을 작성하고 클래스를 복사 불가능으로 표시하십시오. 이미 연결되어있는 인스턴스에 의존하는 경우에는 생성자를 비공개로 만들고 정적 팩토리 메서드를 작성하여 미리 연결된 인스턴스를 가져옵니다.

1

연결에 시간이 오래 걸리면 다른 방법으로 코드를 추가하는 것이 더 합리적입니다. 그래도 예외를 사용하여 오류 코드를 반환하는 대신 connect() 메서드가 성공했는지 여부를 호출자에게 알릴 수 있습니다.

일반 데이터를 던지거나 다른 STL 예외를 throw하는 대신 std::exception에서 파생 된 새 예외 클래스를 만드는 것이 좋습니다. 오류에 대한보다 구체적인 설명 (예 : std::runtime_error에서 파생 됨)에서 예외 클래스를 파생시킬 수도 있지만이 방법은 일반적이지 않습니다.

1

나는 옵션 1이 더 나은 접근법이라고 생각하지만 클래스의 소비자가 이것을 어떻게 사용할 것으로 생각합니까? 그들이 유선으로 연결했다는 사실만으로도 좋은 결과를 얻을 수 있습니다. (옵션 1) 또는 Connect()를 호출 할 수있는 옵션이 있어야합니다 (옵션 2).

RAII는 DRY 원칙도 지원합니다 (반복하지 마십시오). 그러나 옵션 1을 사용하면 예외 처리가 제대로 작동하고 경쟁 조건에 빠지지 않도록해야합니다. 아시다시피, 생성자에 예외가 발생하면 소멸자는 정리를 위해 호출되지 않습니다. 또한 주변에 자물쇠가 있어야 할 정적 기능이 다양 할 수 있으므로 나선형 경로로 안내 할 수 있습니다.

아직 읽어 보지 않았다면 this post입니다.

0

생각한 RAII 마음에서 정의 상 이것이 좋지 않습니까? 획득은 초기화입니다.

3

던진 예외에 관해서는, 완벽하게 자신의 클래스를 만들 수 있습니다. 아마 std :: exception, 또는 std :: runtime_error (ctor에 오류 문자열을 전달할 수 있음)에서 파생 된 경우 선호하는가 상 사용자입니다.

파생 된 유형을 잡을 수 있습니다 원하는 사용자 만의 일반적인 관용구 :

try { 
    operation_that_might_throw(); 
    } catch (std::exception& e) { 
    cerr << "Caught exception: " << e.what() << endl; 
    } 

는 C++ 런타임에 의해 슬로우 아무것도뿐만 아니라 새로운 예외 유형에 대해 작동합니다. 기본적으로 Rule of Least Surprise입니다.

0

연결에 실패하면 연결 개체가 실제로 작동하지 않는 경우 다른 모든 메서드가 항상 아무 작업도 수행하지 않거나 예외를 throw하면 해당 개체를 존재하게하는 것이 바람직하지 않습니다. 이러한 이유로 나는 생성자에서 연결을 수행하고이 메서드가 실패 할 경우 예외 (std::exception에서 파생 됨)를 throw하여 실패합니다.

그러나 클래스의 클라이언트가 생성자가 차단되거나 실패 할 수 있다는 사실을 알고 있어야 할 수도 있습니다. 이런 이유로 나는 생성자를 private으로 만들고 클라이언트가 명시적인 MakeConnection 호출을해야하도록 정적 팩토리 메소드 (생성자 관용법)를 사용하도록 선택할 수 있습니다.

연결이 끊어 졌는지 또는 오프라인 모드를 처리 할 수 ​​있는지 여부를 결정하는 것은 고객의 책임입니다. 전자의 경우 값으로 연결을 소유 할 수 있으며 모든 연결 실패가 클라이언트에 전파되도록 할 수 있습니다. 후자에서는 포인터를 통해 오브젝트를 소유 할 수 있으며, '스마트'가 바람직합니다. 후자의 경우 생성자에서 소유 된 연결의 생성을 시도하거나 필요할 때까지 지연시킬 수 있습니다.

예. (경고 : 코드는 모두 완전히 테스트되지 않았습니다.)

class Connection 
{ 
    Connection(); // Actually make the connection, may throw 
    // ... 

public: 
    static Connection MakeConnection() { return Connection(); } 

    // ... 
}; 

다음은 작동 연결이 필요한 클래스입니다.

class MustHaveConnection 
{ 
public: 
    // You can't create a MustHaveConnection if `MakeConnection` fails 
    MustHaveConnection() 
     : _connection(Connection::MakeConnection()) 
    { 
    } 

private: 
    Connection _connection; 
}; 

다음은 하나도없이 작동하는 클래스입니다.

class OptionalConnection 
{ 
public: 
    // You can create a OptionalConnectionif `MakeConnection` fails 
    // 'offline' mode can be determined by whether _connection is NULL 
    OptionalConnection() 
    { 
     try 
     { 
      _connection.reset(new Connection(Connection::MakeConnection())); 
     } 
     catch (const std::exception&) 
     { 
      // Failure *is* an option, it would be better to capture a more 
      // specific exception if possible. 
     } 
    } 

    OptionalConnection(const OptionalConnection&); 
    OptionalConnection& operator=(const OptionalConnection&); 

private: 
    std::auto_ptr<Connection> _connection; 
} 

마지막으로 필요에 따라 하나를 만들고 호출자에게 예외를 전파합니다.

class OnDemandConnection 
{ 
public: 
    OnDemandConnection() 
    { 
    } 

    OnDemandConnection(const OnDemandConnection&); 
    OnDemandConnection& operator=(const OnDemandConnection&); 

    // Propgates exceptions to caller 
    void UseConnection() 
    { 
     if (_connection.get() == NULL) 
      _connection.reset(new Connection(Connection::MakeConnection())); 

     // do something with _connection 
    } 

private: 
    std::auto_ptr<Connection> _connection; 
} 
0

내 원래의 게시물에 불분명 또 다른 것은이 연결된 후 클라이언트 코드가이 객체와의 상호 작용을하지 않는다는 것입니다. 클라이언트는 자체 스레드에서 실행되고 객체가 인스턴스화되고 연결되면 클라이언트는 상위 프로세스의 지속 기간 동안 실행되는 하나의 메소드를 호출합니다. 해당 프로세스가 종료되면 (어떤 이유로 든) 개체가 연결 해제되고 클라이언트 스레드가 종료됩니다. 원격 프로세스를 사용할 수 없으면 스레드가 즉시 종료됩니다. 따라서 연결되지 않은 객체를 놓는 것은 실제로 문제가되지 않습니다.

나는 생성자에서 연결을 할 수없는 또 다른 이유를 발견 : 그것은 내가 중 하나가 드 structor에 분해 처리, 또는 재미 냄새 별도의 connect() 전화와 별도의 disconnect() 전화를해야 할 필요가 있다는 것을 의미한다. 해체는 어렵지 않고 막거나 던질 수 있으므로 소멸자에서 처리하는 것이 이상적이지 않습니다.