Зачем использовать функции begin и end, не являющиеся членами, в C++11?



каждый стандартный контейнер имеет begin и end метод возврата итераторов для этого контейнера. Однако C++11, по-видимому, ввел свободные функции под названием std::begin и std::end, которую называют begin и end функции-члены. Итак, вместо того, чтобы писать



auto i = v.begin();
auto e = v.end();


ты бы написал



using std::begin;
using std::end;
auto i = begin(v);
auto e = end(v);


в своем выступлении, Написание Современного C++, Херб Саттер говорит, что вы всегда должны использовать бесплатный функции теперь, когда вы хотите начать или закончить итератор для контейнера. Однако он не вдается в подробности относительно почему вы хотели бы. Глядя на код, он сохраняет вам все один символ. Таким образом, насколько стандартные контейнеры идут, свободные функции кажутся совершенно бесполезными. Херб Саттер указал, что существуют преимущества для нестандартных контейнеров, но опять же, он не вдавался в подробности.



Итак, вопрос в том, что именно делают бесплатные версии функций из std::begin и std::end не вызывайте их соответствующие версии функций-членов, и почему вы хотите их использовать?

663   6  

6 ответов:

Как вы называете .begin() и .end() на C-массива ?

свободные функции позволяют более общее программирование, потому что они могут быть добавлены впоследствии, на структуру данных вы не можете изменить.

рассмотрим случай, когда у вас есть библиотека, которая содержит класс:

class SpecialArray;

он имеет 2 метода:

int SpecialArray::arraySize();
int SpecialArray::valueAt(int);

чтобы перебирать его значения, вам нужно наследовать от этого класса и определить begin() и end() методы для случая, когда

auto i = v.begin();
auto e = v.end();

но если вы всегда используете

auto i = begin(v);
auto e = end(v);

вы можете сделать это:

template <>
SpecialArrayIterator begin(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, 0);
}

template <>
SpecialArrayIterator end(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, arr.arraySize());
}

здесь SpecialArrayIterator что-то вроде:

class SpecialArrayIterator
{
   SpecialArrayIterator(SpecialArray * p, int i)
    :index(i), parray(p)
   {
   }
   SpecialArrayIterator operator ++();
   SpecialArrayIterator operator --();
   SpecialArrayIterator operator ++(int);
   SpecialArrayIterator operator --(int);
   int operator *()
   {
     return parray->valueAt(index);
   }
   bool operator ==(SpecialArray &);
   // etc
private:
   SpecialArray *parray;
   int index;
   // etc
};

теперь i и e может быть законно используется для итерации и доступа к значениям SpecialArray

С помощью begin и end свободные функции добавляют один слой косвенности. Обычно это делается для обеспечения большей гибкости.

в этом случае я могу придумать несколько применений.

наиболее очевидное использование-это c-массивы (а не указатели c).

другой - при попытке использовать стандартный алгоритм на несоответствующем контейнере (т. е. контейнер отсутствует .begin() метод). Предполагая, что вы не можете просто исправить контейнер, следующим лучшим вариантом является перегрузка элемент . Херб предлагает вам всегда использовать begin функция для того чтобы повысить единообразие и последовательность в вашем коде. Вместо того, чтобы помнить, какие контейнеры поддерживают метод begin а какая нужна функция begin.

в стороне, следующий c++ rev должен скопировать D псевдо-член нотации. Если a.foo(b,c,d) Не определен он вместо этого пытается foo(a,b,c,d). Это просто немного синтаксического сахара, чтобы помочь нам, бедным людям, которые предпочитают тему потом порядок глаголов.

чтобы ответить на ваш вопрос, свободные функции begin() и end() по умолчанию не делают ничего, кроме вызова члена контейнера .начаться и. конец() функции. От <iterator>, включается автоматически при использовании любой из стандартных контейнеров, таких как <vector>,<list> и т. д. вы получаете:

template< class C > 
auto begin( C& c ) -> decltype(c.begin());
template< class C > 
auto begin( const C& c ) -> decltype(c.begin()); 

вторая часть вашего вопроса заключается в том, почему вы предпочитаете свободные функции, если все, что они делают, это вызов функций-членов в любом случае. Это действительно зависит от того, какой объект v в вашей пример кода. Если тип v является стандартным типом контейнера, например vector<T> v; тогда не имеет значения, используете ли вы функции free или member, они делают то же самое. Если ваш объект v является более общим, как в следующем коде:

template <class T>
void foo(T& v) {
  auto i = v.begin();     
  auto e = v.end(); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

затем с помощью функций-членов разбивает код для массивов T = C, строк C, перечислений и т. д. Используя функции, не являющиеся членами, вы рекламируете более общий интерфейс, который люди могут легко расширить. С помощью свободной функции интерфейс:

template <class T>
void foo(T& v) {
  auto i = begin(v);     
  auto e = end(v); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

код теперь работает с массивами T = C и строками C. Теперь пишем небольшое количество кода адаптера:

enum class color { RED, GREEN, BLUE };
static color colors[]  = { color::RED, color::GREEN, color::BLUE };
color* begin(const color& c) { return begin(colors); }
color* end(const color& c)   { return end(colors); }

мы можем получить ваш код, чтобы быть совместимым с iterable перечислений тоже. Я думаю, что главный смысл Herb заключается в том, что использование свободных функций так же просто, как использование функций-членов, и это дает вашему коду обратную совместимость с типами последовательностей C и прямую совместимость с типами последовательностей, отличными от STL (и будущие типы stl!), с низкой ценой для остальные разработчики.

в то время как функции, не являющиеся членами, не обеспечивают никаких преимуществ для стандартных контейнеров, их использование обеспечивает более последовательный и гибкий стиль. Если вы в какой-то момент хотите расширить существующий класс контейнера, отличный от std, вы бы предпочли определить перегрузки свободных функций, а не изменять определение существующего класса. Поэтому для контейнеров, отличных от std, они очень полезны и всегда используют свободные функции, что делает ваш код более гибким, поскольку вы можете заменить контейнер std на A контейнер без std более прост и базовый тип контейнера более прозрачен для вашего кода, поскольку он поддерживает гораздо более широкий спектр реализаций контейнеров.

но, конечно, это всегда должно быть взвешено правильно и над абстракцией тоже не очень хорошо. Хотя использование свободных функций не является такой уж чрезмерной абстракцией, тем не менее она нарушает совместимость с кодом C++03, который в этом молодом возрасте C++11 все еще может быть проблемой для вас.

одно преимущество std::begin и std::end это то, что они служат точками расширения для реализации стандартного интерфейса для внешних классов.

если вы хотите использовать CustomContainer класс с диапазоном на основе цикла или шаблона функция, которая ожидает .begin() и .end() методы, вы, очевидно, должны реализуйте эти методы.

если класс предоставляет эти методы, это не проблема. Когда это не так, вам придется изменить его*.

это не всегда возможно, например при использовании внешней библиотеки, особенно коммерческий и закрытый источник один.

в таких ситуациях std::begin и std::end пригодится, так как можно обеспечить итератор API без изменения самого класса, а перегрузка функций.

пример: предположим, что вы хотели бы реализовать count_if функция, которая принимает контейнер вместо пары итераторов. Такой код может выглядеть так это:

template<typename ContainerType, typename PredicateType>
std::size_t count_if(const ContainerType& container, PredicateType&& predicate)
{
    using std::begin;
    using std::end;

    return std::count_if(begin(container), end(container),
                         std::forward<PredicateType&&>(predicate));
}

теперь, для любого класса, который вы хотели бы использовать с этим пользовательским count_if, у вас есть только чтобы добавить две свободные функции вместо изменения этих классов.

теперь у C++ есть механизм под названием Аргумент Зависимого Поиска (АДЛ), что делает такой подход еще более гибким.

короче говоря, ADL означает, что когда компилятор разрешает неквалифицированную функцию (т. е. функция без пространства имен, например begin вместо std::begin), Он также рассмотрим функции, объявленные в пространстве имен ее аргументов. Например:

namesapce some_lib
{
    // let's assume that CustomContainer stores elements sequentially,
    // and has data() and size() methods, but not begin() and end() methods:

    class CustomContainer
    {
        ...
    };
}

namespace some_lib
{    
    const Element* begin(const CustomContainer& c)
    {
        return c.data();
    }

    const Element* end(const CustomContainer& c)
    {
        return c.data() + c.size();
    }
}

// somewhere else:
CustomContainer c;
std::size_t n = count_if(c, somePredicate);

в этом случае не имеет значения, что квалифицированные имена some_lib::begin и some_lib::end - с CustomContainer находится в some_lib:: кроме того, компилятор будет использовать эти перегрузки в count_if.

это также причина для того, чтобы иметь using std::begin; и using std::end; in count_if. Это позволяет использовать неквалифицированных begin и end, поэтому с учетом ADL и разрешить компилятору выберите std::begin и std::end когда другие альтернативы не найдены.

мы можем съесть печенье и иметь печенье-т. е. иметь способ обеспечить пользовательскую реализацию из begin/end в то время как компилятор может вернуться к стандартным.

некоторые замечания:

  • по той же причине, есть и другие подобные функции: std::rbegin/rend, std::size и std::data.

  • как и другие ответы упоминает, std:: версии имеют перегрузки для голых массивов. Это полезно, но это просто частный случай того, что я описал выше.

  • используя std::begin и друзья особенно хорошая идея при написании кода шаблона, потому что это делает эти шаблоны более универсальный. Для не-шаблона вы можете просто а также использовать методы, когда это применимо.

P. S. Я осознаю, что этот пост почти 7 лет. Я наткнулся на это, потому что хотел ответить вопрос, который был отмечен как дубликат и обнаружил, что здесь нет ответа на ADL.

Comments

    Ничего не найдено.