Вызов некоторого метода у всех элементов контейнера в C++

Не раз замечаю, что люди абсолютно не знают STL. А зря, ведь стандартная библиотека шаблонов предлагает широкий спектр ассортимента на все случаи жизни. Многие и впрямь считают, что STL — это исключительно контейнеры, что меня, признаться, пугает. Поэтому, в этом и последующем постах, я хочу написать о некоторых возможностях и функциональностях STL, о которых новички (я надеюсь, что только новички) и не подозревают.

Я сторонник обучения на примерах, поэтому, начну с кода. К примеру, есть некий класс Foo:

class Foo
{
public:
    void doSomething()const { std::cout << "doSomething()"  << std::endl; }
};

Объекты этого класса находятся в некотором контейнере, допустим, векторе. Задача состоит в том, чтобы вызвать метод doSomething() у всех объектов находящихся в контейнере.

Первое, о чем подумают многие - это цикл:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
    it->doSomething();

Но циклы — это не "кашерно"... Ведь мы знаем об алгоритме std::for_each и функциональных объектах, которые, кстати говоря, хорошо оптимизируются компиляторами, что позволяет писать более производительный код.

struct DoMethod : public std::unary_function<Foo,void>
{
    void operator() (const Foo& foo) { foo.doSomething(); }
};

std::for_each(vec.begin(), vec.end(), DoMethod());

Конечно, часть из вас знает о новом стандарте C++0x и о его лямбда-функциях. Это позволит написать код короче, проще и, во многих случаях, понятней:

std::for_each(vec.begin(), vec.end(), [](const Foo& foo) {
    foo.doSomething();
});

То, что я упомянул выше, вы, возможно, знали.. а может и не знали — не имеет значение. Но то, что я покажу ниже, обычно не знают многие начинающие. В STL существует интересный способ, позволяющий указать имя метода, который необходимо вызвать:

std::for_each(vec.begin(), vec.end(), std::mem_fun_ref(&Foo::doSomething));

Как по мне, это не менее читабельно приведенных выше способов. К тому же, не менее производительно, чем при использовании функционального объекта.

std::mem_fun_ref является функцией-адаптером, которая создает и передает в алгоритм std::for_each функциональный объект.

Я рекомендую использование именно этот способ. Почему? Да потому что он:

  • нагляден - функциональные объекты частенько описываются в местах далеких от применения;
  • прост в написании;
  • производительней цикла for;

Конечно, лямбда-функции тоже удовлетворяют указанным выше преимуществам. Поэтому, их применение тоже приветствуется. Однако, стандарт еще не вышел, и хотя современные компиляторы поддерживают его в той или иной мере, пройдет не мало времени, пока он не станет корпоративным стандартом! :)

p.s: В посте я не упомянул еще один способ, который является альтернативой циклу for, указанному выше. Как известно, новый стандарт вводит понятие range-based for цикла. Его суть заключается в написании for each цикла в стиле Java, путем скрытого вызова методов begin() и end() у контейнера:

for (const Foo& foo : vec)
    foo.doSomething();

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