2011-03-16 8 views
3

주어진 클래스 Foo템플릿 유형에 기반한 C++ 템플릿 클래스 리팩토링

template <typename T> 
class Foo 
{ 
public: 

    ...other methods.. 

    void bar() 
    { 
    ... 
    m_impl.doSomething(); 
    ... 
    } 

    void fun() 
    { 
    ... 
    m_impl.doSomethingElse(); 
    ... 
    } 

    void fubar() 
    { 
    ... 
    } 

private: 
    T m_impl; 
}; 

나는 T가 boost :: shared_ptr 인 상황에 맞추고 싶었다. 이 경우에는 클래스 Foo의 유일한 변경 사항은

m_impl->doSomething(); 
대신

m_impl.doSomething(); 

같은 헤더에서 FooPtr을 정의하는 결과가 발생했습니다.

template <typename T> 
class FooPtr 
{ 
public: 
    ...other methods.. 

    void bar() 
    { 
    ... 
    m_pImpl->doSomething(); 
    ... 
    } 

    void fun() 
    { 
    ... 
    m_pImpl->doSomethingElse(); 
    ... 
    } 

    void fubar() 
    { 
    ... 
    } 

private: 
    boost::shared_ptr<T> m_pImpl; 
}; 

이제 접근법은 내가 Foo, 과 함께 사용하고자하는 모든 클래스에서 작동합니다. 문제는 내가 중복 코드를 많이 가지고 있고 Foo에 대해 을 변경했기 때문에 FooPtr로 보내야한다는 것입니다.

어떻게 코드를 리팩토링 할 수 있습니까? 예 : T가 boost :: shared_ptr 유형 인 경우 컴파일 타임에 결정할 수있는 방법이 있습니까? -> 연산자를 호출하는 bar 및 fun 메서드 만 전문으로 할 수 있습니까?

편집 : 지금까지 모든 답변 주셔서 감사합니다! 시간을 들여 모든 것을 처리하고 어떤 솔루션이 우리 소프트웨어에 가장 적합한 지 확인하십시오.

편집 2 : @Matthieu : 이것은

class FooImpl 
{ 
public: 
    void doIt() 
    { 
    cout << "A" << std::endl; 
    } 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    Foo<FooImpl> foo; 
    foo.doSomething(); 
    return 0; 
} 
를 사용하고있는 테스트 코드입니다.

답변

4

실뱅은 DRY 솔루션을 작성했지만 학대 상속을 좋아하지 않습니다.

인터페이스를 균일화하기 위해 래퍼 클래스를 사용하는 것이 쉽습니다. 특히 포인터 의미 체계가 매우 잘 작동하기 때문입니다.

namespace details { 
    template <typename T> 
    struct FooDeducer { 
    typedef boost::optional<T> type; 
    }; 

    template <typename T> 
    struct FooDeducer< T* > { 
    typedef T* type; 
    }; 

    template <typename T> 
    struct FooDeducer< boost::shared_ptr<T> > { 
    typedef boost::shared_ptr<T> type; 
    }; 
} // namespace details 

template <typename T> 
class Foo { 
public: 
    // methods 
    void doSomething() { impl->doIt(); } 

private: 
    typedef typename details::FooDeducer<T>::type Type; 
    Type impl; 
}; 

여기서 OptionalPointee 의미를 제공 boost::optional에 의존, 우리는 거의 포인터보다 같은 동작을 얻을.

한 가지 강조하고 싶은 점은 복사 동작의 차이입니다. boost::optional은 전체 복사본을 제공합니다.

+0

나는 당신의 접근법을 사용하려하지만, optional.hpp 내부에서 주장을 얻는다 : this-> is_initialized()는 실패한다. 이전에는 선택 사항을 사용 해 본 적이 없으며 테스트 코드를 추가했습니다. 내가 뭘 놓치고 있니? – Ralf

+0

@Ralf : 기본적으로'boost :: optional'은 비어 있습니다 (널 포인터처럼). 기본값을 원하면 이니셜 라이저 목록에'impl (T())'와 같은 값을 명시 적으로 지정해야합니다 생성 된 개체. –

+0

아, 고마워, 트릭을 했어. – Ralf

0

을 사용 된 테스트 코드입니다.

template < typename T > 
class FooBase 
{ 
private: 
    T m_impl; 

protected: 
    T& impl() { return m_impl; } 
}; 

template < typename T > 
class FooBase< boost::shared_ptr<T> > 
{ 
private: 
    boost::shared_ptr<T> m_impl; 

protected: 
    T& impl() { return *(m_impl.operator ->()); } 
}; 

template < typename T > 
class Foo : protected FooBase<T> 
{ 
public: 
    void bar() 
    { 
     impl().DoSomething(); 
    } 
}; 

이제, 당신은 단지 한 번만 Foo 클래스를 코딩 할 필요가 :

template <typename T> 
class Foo 
{ 
public: 
    //... 
}; 

template<typename T> class Foo<boost::shared_ptr<T>> { 
    //... implement specialization here 
}; 
1

당신은 그런 일을 또 다른 중간 템플릿 클래스를 도입 할 수 있습니다. FooBase에서 부분 특수화를 수행하여 다른 스마트 포인터 유형에 맞게 특수화 할 수 있습니다.

편집 : 당신은 또한 (이 경우, 나는 아마 그런 FooHelper 또는 무언가에 이름을 바꿀 것) 대신 FooFooBase 사이의 상속 관계를 가진의 구성을 사용할 수 있습니다. 당신이 모두 여기에 템플릿을 사용할지 여부를

template < typename T > 
class FooHelper 
{ 
private: 
    T m_impl; 

public: 
    T& impl() { return m_impl; } 
}; 

template < typename T > 
class FooHelper< boost::shared_ptr<T> > 
{ 
private: 
    boost::shared_ptr<T> m_impl; 

public: 
    T& impl() { return *(m_impl.operator ->()); } 
}; 

template < typename T > 
class Foo 
{ 
private: 
    FooHelper<T> m_helper; 

public: 
    void bar() 
    { 
     m_helper.impl().DoSomething(); 
    } 
}; 
+1

나는 반복을 최소화 아이디어를 좋아하지만, 도움이되지만이 작업에 더 적합한 것 상속 및 구성의 남용이라고 생각 할 수 없다. –

2
class A 
{ 
public: 
    void doSomething() {} 
}; 
template <typename T> 
class Foo 
{ 
public: 
    void bar() 
    { 
    Impl(m_impl).doSomething(); 
    } 

private: 
    template<typename P> 
    P& Impl(P* e) 
    { 
     return *e; 
    } 
    template<typename P> 
    P& Impl(std::shared_ptr<P> e) 
    { 
     return *e; 
    } 
    template<typename P> 
    P& Impl(P& e) 
    { 
     return e; 
    } 
    T m_impl; 
}; 
1

정말 질문. 템플릿 매개 변수에는 매우 명확한 인터페이스가 있으므로 추상 기본 클래스 만 사용해야하는 것처럼 보입니다.

인스턴스가 실제로 필요합니까? 개체가 표현되는 방식을 변경해야하는 경우이를 사용하는 템플릿의 일부가 아닌 별도의 연습으로 수행해야합니다.

+0

Tx에 대한 답변은 OO 접근법을 먼저 시도하고 템플릿을 사용하여 설계 문제를 해결했습니다 (OO는 소멸자의 가상 메소드를 호출 할 수 없음) – Ralf

+0

아마도 그 해결책이있을 것입니다. 함수가 소멸자보다 먼저 호출되고 소멸자가 호출되지 않도록 보장해야합니다. – CashCow

+0

제 경우에는 클래스가 파괴되기 전에 메서드를 호출해야합니다. 따라서 소멸자 내에서 호출해야합니다. 템플릿 클래스의 사용자에게 부담을 남기고 싶지 않으므로 반드시 소멸자 내에서 호출해야합니다. – Ralf

2

obj 유형에 따라 obj.f() 또는 obj->f()이라는 구문을 사용하여 함수를 호출하는 클래스 템플릿 caller을 작성할 수 있습니다. 이 샘플 클래스에서 사용되는

template<typename T> 
struct caller 
{ 
    static void call(T &obj) { obj.f(); } //uses obj.f() syntax 
}; 

template<typename T> 
struct caller<T*> 
{ 
    static void call(T* obj) { obj->f(); } //uses obj->f() syntax 
}; 

그리고이 caller 클래스 템플릿 : 여기

이 방법을 보여줍니다 작은 예입니다 http://www.ideone.com/H18n7

:

template<typename T> 
struct X 
{ 
    T obj; 
    X(T o) : obj(o) {} 
    void h() 
    { 
     caller<T>::call(obj); //this selects the appropriate syntax! 
    } 
}; 

ideone에서이 온라인 실행 데모를 참조하십시오

-

편집 :

이것은 더욱 일반적입니다. 여기에서는 caller에 전화를 걸려는 기능을 전달할 수도 있습니다. 이제 caller은 호출 할 함수가 하드 코딩되지 않았습니다!

http://www.ideone.com/83H52

+0

'caller'에서'T'의 인터페이스를 복제 할 필요가 없습니까? – davka

+0

@davka : 편집을 참조하십시오. :-) – Nawaz

+1

+1 또한 좋게 작동합니다, 감사합니다 :) – Ralf

관련 문제