58 votes

Les conteneurs personnalisés doivent-ils avoir des fonctions de début / fin gratuites?

Lors de la création d'un conteneur personnalisé classe qui joue selon les règles habituelles (c'est à dire fonctionne avec des algorithmes de la STL, travaille avec le bon comportement de code générique, etc.), en C++03, il a été suffisant pour mettre en œuvre itérateur de soutien et membre de début/fin des fonctions.

C++11 introduit deux nouveaux concepts de gamme à base de boucle et std::begin/end. Gamme à base de boucle comprend membre de début/fin de fonctions, de sorte que toute C++03 conteneurs gamme de support pour la sortie de la boîte. Pour les algorithmes de la méthode recommandée (selon "l'Écriture moderne de code C++" par Herb Sutter) est d'utiliser std::commencent à la place de la fonction membre.

Cependant, à ce stade, je dois demander - est la méthode recommandée pour l'appel complet de la (des) fonction (c'est à dire std::begin(c)) ou de s'appuyer sur les AVQ et l'appel de commencer(c)?

ADL semble inutile dans ce cas particulier - depuis std::begin(c) les délégués c.begin (), si possible, d'habitude ADL avantages ne semblent pas s'appliquer. Et si tout le monde commence à s'appuyer sur les AVQ, tous les conteneurs ont à mettre en œuvre extra begin () et end() fonctions libres dans leur requises espaces de noms. Cependant, plusieurs sources semblent indiquer que les appels non qualifiés de début/fin sont la méthode recommandée (c'est à dire https://svn.boost.org/trac/boost/ticket/6357).

Donc, qu'est-ce que le C++11? Devrait conteneur bibliothèque des auteurs écrivent extra début/fin de fonctions pour leurs classes à l'appui sans réserve de commencer/terminer des appels en absence de using namespace std; ou en utilisant std::begin;?

35voto

TemplateRex Points 26447

Il existe plusieurs approches, chacun avec leurs propres avantages et inconvénients. Ci-dessous trois approches, avec une analyse coûts-avantages.

ADL par le biais de la coutume non-membre de l' begin() / end()

La première variante de l'offre non-membre de l' begin() et end() les modèles de fonction à l'intérieur d'un legacy de l'espace de noms de modernisation de la fonctionnalité requise sur une catégorie ou d'une classe template qui peut le fournir, mais a par exemple un mauvais conventions de nommage. Code appelant peut alors compter sur l'ADL de trouver ces nouvelles fonctions. Exemple de code (sur la base des observations par @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";
}

Pros: cohérence et laconique convention d'appel qui fonctionne complètement générique

  • fonctionne pour n'importe quel Conteneur Standard et de l'utilisateur-types qui définissent membre .begin() et .end()
  • oeuvres de style C tableaux
  • peut être monté au travail (également pour les de gamme pour les boucles!) pour tout modèle de classe legacy::Container<T> qui n'ont pas membre de l' .begin() et end() sans nécessiter de modifications de code source

Inconvénients: nécessite l'utilisation de déclarations dans de nombreux endroits

  • std::begin et std::end sont nécessaires à ont été introduites dans chaque explicite l'appel de la portée de " retour d'options pour le style C tableaux (écueil potentiel pour modèle des en-têtes et de nuisance générale)

ADL par le biais de la coutume non-membre de l' adl_begin() et adl_end()

Une deuxième solution consiste à encapsuler les à l'aide de déclarations de la solution précédente dans un distinct adl de l'espace de noms par non-membre, les modèles de fonction adl_begin() et adl_end(), qui peut également être trouvé par le biais de l'ADL. Exemple de code (sur la base des observations par @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";
}

Pros: la constance de la convention d'appel qui fonctionne complètement générique

  • même les pros comme pour @Xeo la suggestion de +
  • le répétées à l'aide de déclarations ont été encapsulés (SEC)

Inconvénients: un peu verbeux

  • adl_begin() / adl_end() n'est pas aussi laconique qu' begin() / end()
  • c'est peut-être pas aussi idiomatiques (même si c'est explicite)
  • dans l'attente de C++14 type de retour de la déduction, sera également de polluer l'espace de noms avec std::begin / std::end

REMARQUE: vous ne savez Pas si c'est vraiment améliore l'approche précédente.

Explicitement de qualification std::begin() ou std::end() partout

Une fois que le niveau de verbosité des begin() / end() a été donné de toute façon, pourquoi ne pas revenir à la qualification des appels d' std::begin() / std::end()? Exemple de 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: la constance de la convention d'appel qui fonctionne presque de façon générique

  • fonctionne pour n'importe quel Conteneur Standard et de l'utilisateur-types qui définissent membre .begin() et .end()
  • oeuvres de style C tableaux

Inconvénients: un peu verbeux et de la modernisation n'est pas générique et un problème de maintenance

  • std::begin() / std::end() est un peu plus prolixe que d' begin() / end()
  • ne peut être monté en rattrapage de travail (également pour les de gamme pour les boucles!) pour toute la classe LegacyContainer qui n'ont pas membre de l' .begin() et end() (et pour lequel il n'y a pas de code source!) en fournissant explicite spécialisations de la non-membre, les modèles de fonction begin() et end() en namespace std
  • ne peut être monté sur des modèles de classe LegacyContainer<T> en ajoutant directement des fonctions de membre begin() / end() à l'intérieur du code source de l' LegacyContainer<T> (pour les modèles est disponible). L' namespace std astuce ne fonctionne pas ici, parce que les modèles de fonction ne peuvent pas être partiellement spécialisées.

Ce qu'il faut utiliser?

L'ADL approche par le biais de la non-membre de l' begin() / end() dans un récipient propre espace de noms est la idiomatiques C++11 approche, en particulier pour les fonctions génériques qui nécessitent un équipement ultérieur de l'héritage des classes et des modèles de classe. C'est le même langage que pour l'utilisateur-non-membre de l' swap() fonctions.

Pour le code qui utilise uniquement des Conteneurs Standard ou de type C tableaux, std::begin() et std::end() pourrait être appelé partout sans introduire à l'aide de déclarations, au détriment des plus détaillé des appels. Cette approche peut encore être rénovées, mais il nécessite de jongler avec namespace std (pour les types de classe) ou le lieu de la source de modifications (pour les modèles de classe). Il peut être fait, mais c'est pas la peine de la gestion de la difficulté.

Dans le code non générique, où le conteneur en question est connue au codage en temps, on pourrait même compter sur l'ADL pour les Conteneurs Standard uniquement et explicitement qualifier std::begin / std::end C-le style des tableaux. Il perd un peu l'appel de la cohérence, mais enregistre sur l'utilisation de déclarations.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X