programing

커스텀 컨테이너에 무료 시작 / 종료 기능이 있어야합니까?

nasanasas 2020. 12. 13. 09:41
반응형

커스텀 컨테이너에 무료 시작 / 종료 기능이 있어야합니까?


일반적인 규칙 (예 : STL 알고리즘으로 작동, 잘 작동하는 제네릭 코드 등으로 작동)에 따라 작동하는 사용자 정의 컨테이너 클래스를 만들 때 C ++ 03에서는 반복기 지원 및 멤버 시작 / 종료 함수를 구현하는 것으로 충분했습니다.

C ++ 11에는 범위 기반 for 루프와 std :: begin / end의 두 가지 새로운 개념이 도입되었습니다. 범위 기반 for 루프는 멤버 시작 / 종료 함수를 이해하므로 모든 C ++ 03 컨테이너는 즉시 사용 가능한 범위 기반을 지원합니다. 알고리즘의 경우 권장되는 방법 (Hub Sutter의 '최신 C ++ 코드 작성'에 따라)은 멤버 함수 대신 std :: begin을 사용하는 것입니다.

그러나이 시점에서 질문해야합니다. 완전한 begin () 함수 (예 : std :: begin (c))를 호출하거나 ADL에 의존하고 begin (c)를 호출하는 권장 방법은 무엇입니까?

ADL은이 특별한 경우에 쓸모가 없어 보입니다. std :: begin (c)가 가능하면 c.begin ()에 위임하기 때문에 일반적인 ADL 이점이 적용되지 않는 것 같습니다. 그리고 모두가 ADL에 의존하기 시작하면 모든 사용자 지정 컨테이너는 필수 네임 스페이스에 추가 begin () / end () 무료 함수를 구현해야합니다. 그러나 여러 출처에서 시작 / 종료에 대한 비정규 호출이 권장되는 방법임을 암시하는 것 같습니다 (예 : https://svn.boost.org/trac/boost/ticket/6357 ).

그렇다면 C ++ 11 방식은 무엇일까요? 컨테이너 라이브러리 작성자가 네임 스페이스 std를 사용하지 않고 정규화되지 않은 시작 / 종료 호출을 지원하기 위해 클래스에 대한 추가 시작 / 종료 함수를 작성해야합니다. 또는 std :: begin ;? 사용


각각 장단점이있는 몇 가지 접근 방식이 있습니다. 비용 편익 분석을 통한 세 가지 접근 방식 아래.

맞춤 비회원을 통한 ADL begin()/end()

첫 번째 대안은 네임 스페이스 내에 비 멤버 begin()end()함수 템플릿을 제공 legacy하여 필요한 기능을 제공 할 수 있지만 예를 들어 잘못된 명명 규칙이있는 모든 클래스 또는 클래스 템플릿에 추가합니다. 그런 다음 호출 코드는 ADL을 사용하여 이러한 새 함수를 찾을 수 있습니다. 예제 코드 (@Xeo의 주석 기반) :

// LegacyContainerBeginEnd.h
namespace legacy {

// retro-fitting begin() / end() interface on legacy 
// Container class template with incompatible names         
template<class C> 
auto begin(Container& c) -> decltype(c.legacy_begin())
{ 
    return c.legacy_begin(); 
}

// similarly for begin() taking const&, cbegin(), end(), cend(), etc.

} // namespace legacy

// print.h
template<class C>
void print(C const& c)
{
    // bring into scope to fall back on for types without their own namespace non-member begin()/end()
    using std::begin;
    using std::end;

    // works for Standard Containers, C-style arrays and legacy Containers
    std::copy(begin(c), end(c), std::ostream_iterator<decltype(*begin(c))>(std::cout, " ")); std::cout << "\n";

    // alternative: also works for Standard Containers, C-style arrays and legacy Containers
    for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}

장점 : 완전히 일반적으로 작동하는 일관되고 간결한 호출 규칙

  • 멤버를 정의하는 표준 컨테이너 및 사용자 유형에 대한 작품 .begin().end()
  • C 스타일 배열에서 작동
  • 작업에 개조 할 수있다 (도에 대한 루프 범위-에 !) 어떤을위한 클래스 템플릿 legacy::Container<T> 멤버가없는 .begin()end()소스 코드 수정을 요구하지 않고

단점 : 여러 곳에서 사용 선언이 필요합니다.

  • std::beginstd::end(템플릿 헤더 및 일반 귀찮은에 대한 잠재적 인 함정) 모든 C 스타일 배열의 가을 다시 옵션과 같은 명시 적 호출 범위 반입 된해야

맞춤 비회원을 통한 ADL adl_begin()adl_end()

두 번째 대안은 별개로 이전 용액의 사용 선언 - 캡슐화하는 adl비 멤버 함수 템플릿을 제공함으로써 공간 adl_begin()adl_end()다음과 같은 ADL을 통해 찾을 수있다. 예제 코드 (@Yakk의 주석 기반) :

// LegacyContainerBeginEnd.h 
// as before...

// ADLBeginEnd.h
namespace adl {

using std::begin; // <-- here, because otherwise decltype() will not find it 

template<class C> 
auto adl_begin(C && c) -> decltype(begin(std::forward<C>(c)))
{ 
    // using std::begin; // in C++14 this might work because decltype() is no longer needed
    return begin(std::forward<C>(c)); // try to find non-member, fall back on std::
}

// similary for cbegin(), end(), cend(), etc.

} // namespace adl

using adl::adl_begin; // will be visible in any compilation unit that includes this header

// print.h
# include "ADLBeginEnd.h" // brings adl_begin() and adl_end() into scope

template<class C>
void print(C const& c)
{
    // works for Standard Containers, C-style arrays and legacy Containers
    std::copy(adl_begin(c), adl_end(c), std::ostream_iterator<decltype(*adl_begin(c))>(std::cout, " ")); std::cout << "\n";

    // alternative: also works for Standard Containers, C-style arrays and legacy Containers
    // does not need adl_begin() / adl_end(), but continues to work
    for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}

장점 : 완전히 일반적으로 작동하는 일관된 호출 규칙

  • @Xeo의 제안과 동일한 장점 +
  • 반복되는 using 선언이 캡슐화되었습니다 (DRY).

단점 : 약간 장황함

  • adl_begin() / adl_end() is not as terse as begin() / end()
  • it is perhaps also not as idiomatic (although it is explicit)
  • pending C++14 return type deduction, will also pollute namespace with std::begin / std::end

NOTE: Not sure if this really improves upon the previous approach.

Explicitly qualifying std::begin() or std::end() everywhere

Once the verbosity of begin() / end() has been given up anyway, why not go back to the qualified calls of std::begin() / std::end()? Example code:

// LegacyIntContainerBeginEnd.h
namespace std {

// retro-fitting begin() / end() interface on legacy IntContainer class 
// with incompatible names         
template<> 
auto begin(legacy::IntContainer& c) -> decltype(c.legacy_begin())
{ 
    return c.legacy_begin(); 
}

// similary for begin() taking const&, cbegin(), end(), cend(), etc.

} // namespace std

// LegacyContainer.h
namespace legacy {

template<class T>
class Container
{
public:
    // YES, DOCUMENT REALLY WELL THAT THE EXISTING CODE IS BEING MODIFIED
    auto begin() -> decltype(legacy_begin()) { return legacy_begin(); }
    auto end() -> decltype(legacy_end()) { return legacy_end(); }

    // rest of existing interface
};

} // namespace legacy

// print.h
template<class C>
void print(C const& c)
{
    // works for Standard Containers, C-style arrays as well as 
    // legacy::IntContainer and legacy::Container<T>
    std::copy(std::begin(c), std::end(c), std::ostream_iterator<decltype(*std::begin(c))>(std::cout, " ")); std::cout << "\n";

    // alternative: also works for Standard Containers, C-style arrays and
    // legacy::IntContainer and legacy::Container<T>
    for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}

Pros: consistent calling convention that works almost generically

  • works for any Standard Container and user-types that define member .begin() and .end()
  • works for C-style arrays

Cons: a little verbose and retrofitting is not generic and a maintainence problem

  • std::begin() / std::end() is a little more verbose than begin() / end()
  • can only be retrofitted to work (also for range-for loops!) for any class LegacyContainer that does not have member .begin() and end() (and for which there is no source code!) by providing explicit specializations of the non-member function templates begin() and end() in namespace std
  • can only be retrofitted onto class templates LegacyContainer<T> by directly adding member functions begin() / end() inside the source code of LegacyContainer<T> (which for templates is available). The namespace std trick does not work here because function templates cannot be partially specialized. 

What to use?

The ADL approach through non-member begin() / end() in a a container's own namespace is the idiomatic C++11 approach, especially for generic functions that require retrofitting on legacy classes and class templates. It is the same idiom as for user-providing non-member swap() functions.

For code that only uses Standard Containers or C-style arrays, std::begin() and std::end() could be called everywhere without introducing using-declarations, at the expense of more verbose calls. This approach can even be retrofitted but it requires fiddling with namespace std (for class types) or in-place source modifcations (for class templates). It can be done, but is not worth the maintainence trouble.

In non-generic code, where the container in question is known at coding-time, one could even rely on ADL for Standard Containers only, and explicitly qualify std::begin / std::end for C-style arrays. It loses some calling consistency but saves on using-declarations.

참고URL : https://stackoverflow.com/questions/17562943/should-custom-containers-have-free-begin-end-functions

반응형