2013-03-06 7 views
1

X : 서로 관련이없는 서로 다른 유형의 객체를 처리하는 비 관련 컨테이너 형 객체 (벡터,지도, 트리 등)의 모음이 있습니다. 이 A 테이너에있는 오브젝트의 수명은 그 중 일부 서브 세트간에 공유됩니다. 나는 그들의 동기화를 담당하는 대상을 가지고있다. 내가 생각할 수있는 동기화 클래스의 가장 간단한 구현은 BaseContainerLike 포인터의 벡터를 갖는 것입니다. BaseContainerLike은 내가 관리하고자하는 모든 컨테이너와 같은 객체에 대한 공통 인터페이스를 구현하는 클래스입니다. 그러나 그들 모두가 용기 같은 것은 아닙니다. 그들은 같이 컨테이너로 사용할 수 있지만 공통 기본 클래스에서 상속받는 것은 매우 이상하게 느껴지고 그것이 내 디자인을 매우 강하게 두려워합니다.상속없는 다형성

그래서 나는이 같은 ContainerLikeInterface 클래스를 만들었습니다

struct ContainerLikeInterface { 
template<T> 
ContainerLikeInterface(T& t) 
: create([](int i){ return t->create(i); }), // this is just an example 
    delete([](int i){ return t->delete(i); }) {} 

std::function<void(int)> create; 
std::function<void(int)> delete; 
}; 

template<class T> 
ContainerLikeInterface make_containerLikeInterface(T& t) { 
    return ContainerLikeInterface(t); 
} 

이 나를 하찮게 (난 그냥 부분적으로 다른 유형의 생성자를 전문 수) 비 관입 방법으로 인터페이스의 벡터를 만들 수 있습니다 . 이 접근 방식을 사용하는 코드는 상속을 사용할 때보 다 약간 빠르지 만 약간 더 많은 메모리와 컴파일 시간이 필요합니다 (그러나 컴파일 시간의 우선 순위는 지정하지 않습니다). 그러나이 방법이 내 프로젝트와 잘 맞는지 잘 모르겠습니다. 그리고 나는 사람들이 인터페이스에 객체의 소유권을 이전하는 것을 선호하는 가치를 의미에 대한 몇 가지 기사를 읽었습니다, 그래서 나는 다음과 같은 질문이 :

  • 어떤이 방법의 장점/단점입니까?
  • 장기적으로 어떤 문제가 생길 수 있습니까?
  • 대신 상속을 사용해야합니까?
  • 다른 방식으로 구현해야합니까?
  • 대신 라이브러리를 사용해야합니까? (boost::TypeErasure, adobe::poly, 또는 pyrtsa/poly)
+1

좋은지 여부는 전적으로 해결하려는 문제에 달려 있습니다.이 문제는 생략했습니다. 즉, 왜이 상황에 처해 있니? – GManNickG

+0

@GManNickG이 상황에서 어떻게 끝났는지 설명하는 구체적인 문제에 대한 추가 정보를 추가했습니다. – gnzlbg

답변

1

기본 아이디어 (상속을 요구하지 않음)가 좋습니다. 대신 Adobe.Poly를 사용하는 것이 좋습니다. 단일 작업 당 1 std::function을 사용하면 N 종류의 가상 테이블 (포인터)과 잠재적으로 N 개의 힙 할당 (SBO (Small Buffer Optimization)을 적용 할 수 있는지 여부에 따라 다름)이 있습니다.

또한 개체 수명 관리 문제가 발생할 가능성이 큽니다. 구현시 실제 오브젝트가 "인터페이스"보다 오래 있다고 가정합니다. 조만간 당신은 그것을 잘못 잡을 것입니다. 이런 이유로 나는 value-semantic 접근을 격려 할 것입니다. Adobe.Poly가 제공합니다.

Adobe.Poly를 사용하면 하나의 vtable (포인터) 만 얻을 수 있습니다. 또한 SBO를 구현합니다. 잠재적으로 단일 할당이 아닙니다.

나는 반드시 Boost.TypeErasure와 함께 가지 않을 것입니다. 그것은 많은 메타 프로그래밍을 이용하는 인터페이스를 지정하는 또 다른 "언어"를 배워야하며 오늘날에는 SBO를 구현하지 않습니다.

Adobe.Poly는 잘 설명되어 있지 않습니다. 사용 방법의 예는 this post을 참조하십시오. 또한 구현 방법은 this paper을 참조하십시오.

+0

안녕하세요, 올해 초 귀하의 게시물에서 폴리에 대해 배웠습니다. 작성해 주셔서 감사합니다. 그러나 나는 기본적으로 가치 의미에 대해 당신과 의견이 다르다. 나는 그것이 더 안전하다는 데 동의하지만, 어느 것이 가장 좋은지는 응용 프로그램에 크게 의존한다고 생각합니다. 퍼포먼스 관점에서, 당신은 다형성 접근을위한 참조 의미론과 monomorphic 접근 (그리고 thight 루프)을위한 값 의미와 배열 컨테이너를 원한다. OTOH 예를 들어 GUI를 다루고 있고 위젯을 가지고 있고 위젯의 정확한 유형을 신경 쓰지 않는다면 가치 의미론은 소유권을 단순화하므로 더 좋습니다. – gnzlbg

+0

흠.참조 또는 값 의미가 필요한지 여부는 응용 프로그램에 따라 다릅니다. 나는 그 성능이 이런 종류의 '응용 프로그램'이라는 것을 확신하지 못합니다. 가치 의미 론적 접근법과 참조 의미론을 사용하는 비용을 측정 한 경우, 판단하는 것이 좋습니다. 참조 의미 체계 사용에 대한 나의 동기는 두 개의 다른 위치에서 매우 동일한 객체에 액세스해야하는 경우입니다 (예 : 상태의 변경 사항을 관찰해야하기 때문에). – Andrzej

0

당신은 본질적으로 어떤 T에 대한 포인터 또는 참조 ContainerLikeInterface 인터페이스 프록시 개체를 만들 수 있습니다.

ContainerLikeInterface을 파생 할 수없는 표준 컨테이너를 계속 사용하면서 프록시를 만들지 않는 방법이 있습니다. 그것은 아마 그것과 같을 것이다 디자인 후두둑 믹스와 C++ (11)의 완벽한 전달로 호출됩니다 std::vector<int> 같은 문제의 컨테이너의 모든 선언,에 있기 때문에 그러나

#include <vector> 

struct IContainerLikeInterface 
{ 
    virtual void create(int) = 0; 
    virtual void erase(int) = 0; 
}; 

template<class T> 
struct ContainerLikeInterface : T, IContainerLikeInterface 
{ 
    template<class... Args> 
    ContainerLikeInterface(Args&& ...args) 
     : T(std::forward<Args>(args)...) 
    {} 

    // implement IContainerLikeInterface 
    void create(int) override; 
    void erase(int) override; 
}; 

int main() { 
    ContainerLikeInterface<std::vector<int> > v(10); 
    v.size(); 
} 

, 그것은 침입입니다 ContainerLikeInterface<std::vector<int> >으로 변경하십시오. ContainerLikeInterface<T>이 -T이기 때문에 사용법은 동일하게 유지됩니다.

+0

감사합니다. 그러나 (CRTP로 구현 된) 믹싱을 사용할 때 각 인터페이스는 다른 유형을 갖습니다. 즉, 인터페이스의 벡터를 가질 수 없습니다. – gnzlbg

+0

@gnzlbg 오, 죄송 합니다만 처음에는 제대로하지 못했습니다. 답변이 업데이트되었습니다. –

+0

답변을 업데이트 해 주셔서 감사합니다. 내 컨테이너는 이미 기본 컨테이너 클래스 (CRTP 사용)의 mixins로 구현됩니다. 이것은 내가 가장 간단한 대안으로 내 포스트에서 정의한 것이다. 이것에 대한 멋진 점은 정적 인 다형성을 "가능한 곳에서"사용할 수 있으며 여전히 동적 인 다형성을 수행 할 수 있다는 것입니다. 그러나 제 컨테이너에는 아주 작은 인라인 함수가 많이 있습니다. 내가 가상 함수를 정의한 순간부터 gcc는 모든 것을 인라이닝하는 것을 멈췄습니다. 그래서 지금 당장하고있는 것으로 전환하는 이유 중 하나입니다 ... 그 중 하나 또는 전체 런타임 다형성으로 전환하십시오 ... – gnzlbg

1

사용자 인터페이스는 Rust object system의 인터페이스 시스템과 약간 비슷합니다. 클래식 VMT 기반 인터페이스에는 VMT에 대한 포인터를 보유하는 객체에 대한 포인터가 있습니다. 당신은 2 개의 포인터를 가지고 있습니다 : 하나는 객체에, 다른 하나는 메소드 테이블에 있습니다. 이미 언급 한 단점 (메모리 사용 등)을 가지고 가상 함수보다 더 많은 힘을 갖는 것처럼 보입니다. 속도에 관해서는 std::function은 표준 할당자를 사용하여 포인터를 t으로 유지합니다. ContainerLikeInterface 생성자를 많이 호출하면 인터페이스에 std::function 당 적어도 하나의 할당이 필요하기 때문에 일부 성능 저하가 발생할 수 있으며이를 위해 직접 작성할 수 있습니다.

+0

그것은 std :: function 구현에 따라 다르지만, http://probablydance.com/2013/01/13/a-faster-implementation-of-stdfunction/ 참조하십시오. – gnzlbg

+0

맞습니다. 감사합니다. 녹에 관한 링크를 고맙습니다. –

+0

정말 재미 있어요! – gnzlbg