2011-09-10 4 views
2

나는 비슷한 방법을 찾아내는 기술을 찾고 있습니다. 문제는 다음과 같습니다. 검색을 수행하려면 컨테이너 내용을 수정할 필요가없는 컨테이너에서 find 메서드가 필요합니다. 그러나 이후에 const 및 비 const 버전이 있어야하며, const_iterator 대신 iterator가 반환되는 경우 컨테이너의 수정으로 이어질 수 있습니다. 이 두 경우 코드는 정확히 동일하며 접근 자만 constXXX 또는 XXX로 평가되고 컴파일러가 작업을 수행합니다. 디자인과 유지 측면에서 보았을 때 두 가지 방법을 두 번 구현하는 것이 현명하지 않습니다. (그리고 난 정말 그것을 위해 매크로를 사용하지 않도록하고 싶습니다 ...) 가 내 말하는 것도 아주 잘 stl_tree.h에서 STL의 GCC 구현에서 코드의 조각에 의해 설명입니다 :팩터링 기법은 메소드와 비슷합니까?

template<typename _Key, typename _Val, typename _KeyOfValue, 
    typename _Compare, typename _Alloc> 
    typename _Rb_tree<_Key, _Val, _KeyOfValue, 
      _Compare, _Alloc>::iterator 
    _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>:: 
find(const _Key& __k) 
{ 
    iterator __j = _M_lower_bound(_M_begin(), _M_end(), __k); 
    return (__j == end() 
     || _M_impl._M_key_compare(__k, 
       _S_key(__j._M_node))) ? end() : __j; 
} 

template<typename _Key, typename _Val, typename _KeyOfValue, 
     typename _Compare, typename _Alloc> 
typename _Rb_tree<_Key, _Val, _KeyOfValue, 
      _Compare, _Alloc>::const_iterator 
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>:: 
find(const _Key& __k) const 
{ 
    const_iterator __j = _M_lower_bound(_M_begin(), _M_end(), __k); 
    return (__j == end() 
     || _M_impl._M_key_compare(__k, 
       _S_key(__j._M_node))) ? end() : __j; 
} 

메소드의 프로토 타입은 다르지만 구현에 작성된 코드는 실제로 동일하다는 것을 알 수 있습니다.

두 가지 가능한 솔루션을 생각해 냈습니다. 첫 번째는 const_cast이고 다른 하나는 도우미 템플릿 구조체입니다.

#include <iostream> 
using namespace std; 

struct Data 
{ 
    typedef int*  iterator; 
    typedef const int* const_iterator; 

    int m; 

    Data():m(-3){} 
}; 

struct A : public Data 
{ 
    const_iterator find(/*const Key& k */) const 
    { 
    A *me = const_cast < A* > (this); 
     return const_iterator(me->find(/*k*/)); 
    } 

    iterator find(/*const Key& k */){ 
    return &m; } 
}; 

//the second one is with the use of an internal template structure: 

struct B : public Data 
{ 

    template<class Tobj, class Titerator> 
    struct Internal 
    { 
     Titerator find(Tobj& obj/*, const Key& k */){ 
     return &(obj.m); } 
    }; 

    const_iterator find(/*const Key& k */) const 
    { 
    Internal<const B, const_iterator> internal; 
    return internal.find(*this/*, k*/); 
    } 

    iterator find(/*const Key& k */) 
    { 
    Internal<B,iterator> internal; 
    return internal.find(*this/*, obs*/); 
    } 
}; 


int main() 
{ 
    { 
    A a; 
    a.find(); 
    A::iterator it = a.find(); 
    cout << *it << endl; 


    const A& a1(a); 
    A::const_iterator cit = a1.find(); 
    cout << *cit << endl; 
    } 

    { 
    B b; 
    b.find(); 
    B::iterator it = b.find(); 
    cout << *it << endl; 


    const B& b1(b); 
    B::const_iterator cit = b1.find(); 
    cout << *cit << endl; 
    } 
} 

그것은 아마 매우 잘 알려진 문제이며, 내가 어떤 C++ 전문가가 문제를 해결하는 좋은 디자인 패턴과 함께 오면 알고 싶다 : 나는 여기 두 가지 방법의 간단한 예를 생산 . 특히 누군가가 두 가지 접근 방식 중 하나를 사용하여 문제를 (특히 공연의 관점에서) 보는지 알고 싶습니다. 처음에는 이해하기가 훨씬 쉽기 때문에, 특히 그것을 읽고 나면 더 좋아할 것입니다. Constants and compiler optimization in C++ const_cast를 작성하고 내 공연을 망칠 수있는 것처럼 보일 것 같습니다.

마누엘에게 미리, 환호에

+2

개인적인 것은 없지만 Q를보고 포기하면 코드가 너무 많이 손상됩니다. : –

+0

'std :: find'의 잘못된 점은 const가 아닌 반복자를 받아들이고 반환합니다. –

+0

std :: find는 반복적으로 (선형 복잡도) 많은 경우에, 특히 위에서 밑줄이 쳐진지도 케이스에서, 그러한 경우에는 자신 만의 구현이 필요하고 좋은 디자인이 필요한 그런 일을하는 것입니다. 물론 문제는 함수를 찾는 것이 아니라 컨테이너를 수정하지만 반복자를 반환하는 함수에 국한됩니다 const 객체와 iterator를 다룰 때 const_iterators를 돌려 주길 원할 것입니다. –

답변

0

이되지 않을 수도 아주 좋은 솔루션을 주셔서 감사합니다. const 과부하와 iterator/const_iterator은 오히려 서투르게 쓸만한 도구입니다.

첫 번째 경우에는 const 버전이 작업을 수행하고 비 const 버전이 캐스팅을 수행하도록하는 것이 좋습니다. 그렇게하면 컴파일러는 알고리즘이 실제로 컨테이너를 수정하지 않는지 확인할 수 있습니다.

캐스팅을 const_iterator에서 iterator으로하면 구현 세부 사항에 따라 다소 어색 할 수 있습니다. 그러나 이것을 한 곳에서 캡슐화하기 위해 개인 도우미를 만들 수 있습니다.

struct A : public Data 
{ 
    iterator find(/*const Key& k */) 
    { 
    const A *me = this; 
    return remove_const_from(me->find(/*k*/)); 
    } 

    const_iterator find(/*const Key& k */) const{ 
    return &m; } 

    private: 
     //could be also static, but in the general case, *this might be needed 
     iterator remove_const_from(const_iterator p) 
     { 
      //in this case just a const_cast 
      return const_cast<int*>(p); 
     } 
}; 

두 번째 경우에는 템플릿 함수와 적어도 인수 유형을 추론 할 수있는 기능을 사용하여 좀 더 자세한 정보를 줄일 수 있습니다. 이 bar의 반환 값을 제공 작품

struct foo 
{ 
    const int* bar() const; 
    int* bar() 
    { 
     const int* p = static_cast<const foo*>(this)->bar(); 

     // Perfectly defined since p is not really 
     // const in the first place 
     return const_cast<int*>(p); 
    } 
}; 

:

struct B : public Data 
{ 
    struct Internal //eventually, could be just a free function? 
    { 
     template<class Titerator, class Tobj> 
     static Titerator find(Tobj& obj/*, const Key& k */){ 
     return &(obj.m); } 
    }; 

    const_iterator find(/*const Key& k */) const 
    { 
    return Internal::find<const_iterator>(*this/*, k*/); 
    } 

    iterator find(/*const Key& k */) 
    { 
    return Internal::find<iterator>(*this/*, obs*/); 
    } 
}; 
1

동일한 구현 CONST 비 const 멤버 함수 간의 코드를 공유하는 관용적 인 방법은 비 CONST 하나 const_cast이고 bar의 구성원 객체입니다. const가 아닌 경우 bar을 호출 할 때 사실 const가 아닙니다 (따라서 const_cast은 합법적입니다).

const가 아닌 버전과 const_cast을 const에 쓸 수 없습니다. 이것은 정의되지 않은 동작입니다. 객체가 처음에 const가 아닌 경우에만 을 제거 할 수 있습니다 (). 귀하의 예제 코드에서

, 당신은 맨손으로 포인터를 사용하기 때문에, 당신이 할 수 있습니다

struct A : public Data 
{ 
    const_iterator find(const Key& k) const 
    { 
     // The real implementation of find is here 
    } 

    iterator find(const Key& k) 
    { 
     // Not the other way around ! 
     const_iterator p = static_cast<const A*>(this)->find(k); 
     return const_cast<iterator>(p); 
    } 
}; 

을하지만, 가능한 빨리 더 복잡한 반복자 유형을 사용,이 작동하지 않습니다 : 사실, 어떤 변환에서이없는 표준 컨테이너 'const_iterator to iterator, 그래서 당신은 일반 포인터를 사용하지 않는 한 망쳐 버렸어.

하나의 해결책은 가능한 한 많은 요소를 제외하고 const_cast이 될 수 있으며 가장 마지막에 반복기를 제조하는 것입니다.

관련 문제