커스텀 컨테이너에 무료 시작 / 종료 기능이 있어야합니까?
일반적인 규칙 (예 : 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::begin
및std::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 asbegin()
/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 thanbegin()
/end()
- can only be retrofitted to work (also for range-for loops!) for any class
LegacyContainer
that does not have member.begin()
andend()
(and for which there is no source code!) by providing explicit specializations of the non-member function templatesbegin()
andend()
innamespace std
- can only be retrofitted onto class templates
LegacyContainer<T>
by directly adding member functionsbegin()
/end()
inside the source code ofLegacyContainer<T>
(which for templates is available). Thenamespace 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
'programing' 카테고리의 다른 글
Codeigniter의 get_instance () : 왜 변수에 할당합니까? (0) | 2020.12.13 |
---|---|
mysql은 null 값을 인덱싱합니까? (0) | 2020.12.13 |
"innerHTML + =…"대 "appendChild (txtNode)" (0) | 2020.12.12 |
C / C ++ 주 함수의 매개 변수는 어디에 있습니까? (0) | 2020.12.12 |
이스케이프 시퀀스 \ f-용지 공급-정확히 무엇입니까? (0) | 2020.12.12 |