Рубиновые блоки, Java-замыкания в C ++
Вопрос
Я разрабатываю программу, в которой я часто так делаю:
void Model::SetCollideMode( const std::string &m )
{
Body *body;
std::map<std::string, Body* >::iterator iter;
for (iter=this->bodies.begin(); iter!=this->bodies.end(); iter++)
{
body = iter->second;
body->SetCollideMode( m );
}
}
У меня есть несколько подобных методов в нескольких объектах, которые в основном применяют свойство ко всем его дочерним элементам. Исходя из мира Ruby, я хочу сделать что-то вроде:
for_all_bodies{ body->SetCollideMode(m) }
Есть ли способ сделать этот код более замыкательным, например или улучшить его?
Я осознаю, как работает C ++, что он основан на стеке, и в нем нет контекстной информации для создания идеального замыкания, подобного функциональности (для этого нужна виртуальная машина?), но, по крайней мере, по сравнению с текущим повторим этот код в 100 раз программирование.
Решение
Есть несколько подходов, но ни один из них не идеален.
Во-первых, «обычный» подход будет заключаться в том, чтобы определить функтор, чтобы делать то, что вам нужно:
struct SetCollideModeFunc {
SetCollideModeFunc(const std::string& m) : m(m) {}
void operator()(std::pair<std::string, Body*>& p) {
Body* b = p.second;
b->SetCollideMode(m);
}
const std::string& m;
};
void Model::SetCollideMode( const std::string &m )
{
std::for_each(bodies.begin(), bodies.end(), SetCollideModeFunc(m));
}
Это не экономит много кода, но позволяет отделить итерацию от операции, которую вы хотите применить. И если вам нужно установить collidemode несколько раз, вы, конечно, можете использовать функтор.
Более короткая версия возможна с библиотекой Boost.Lambda, которая позволит вам определить встроенный функтор. Я не могу вспомнить точный синтаксис, так как я не часто использую Boost.Lambda, но это будет примерно так:
std::for_each(bodies.begin(), bodies.end(), _1.second->SetCollideMode(m));
В C ++ 0x вы получаете языковую поддержку лямбда-выражений, позволяющую использовать синтаксис, подобный этому, без использования сторонних библиотек.
Наконец, Boost.ForEach может быть опцией, допускающей такой синтаксис:
void Model::SetCollideMode(const std::string &m)
{
BOOST_FOREACH ((std::pair<std::string, Body*> p), bodies) // note the extra parentheses. BOOST_FOREACH is a macro, which means the compiler would choke on the comma in the pair if we do not wrap it in an extra ()
{
p.second->SetCollideMode(m);
}
}
Другие советы
В C ++ 0x да. Смотрите здесь. Как вы уже догадались, они сделаны в характерный для C ++ способ, т. е. если вы случайно закроете переменную стека, а затем оставите лямбда-объект дольше, чем стек, то у вас будет неопределенное поведение. Это совершенно новый способ аварийного завершения вашей программы! Но это несправедливо - во многих отношениях они более изощренны, чем лямбды во многих других языках, потому что вы можете объявить, в какой степени им разрешено изменять состояние.
До этого были попытки подражать тому же вещь , но они, вероятно, больше проблем, чем стоит.
BOOST_FOREACH (или новый цикл, основанный на диапазоне) - это, вероятно, верный путь, но вот как я обычно подхожу к лямбде в текущем стандарте, используя tr1 bind:
#include <algorithm>
#include <functional>
using namespace std;
void Model::SetCollideMode( const std::string &m )
{
for_each(bodies.begin(),bodies.end(),
tr1::bind(&Body::SetCollideMode,
tr1::bind(&pair<std::string, Body*>::second, _1), m));
}
Вы можете использовать Boost.Foreach .
#include <boost/foreach.hpp>
void Model::SetCollideMode(const std::string &m)
{
typedef pair<std::string, Body*> body_t;
BOOST_FOREACH (body_t& body, bodies)
{
body.second->SetCollideMode(m);
}
}
C ++ пока не поддерживает лямбда-выражения. Я иногда использую этот обходной путь:
#include <boost/bind.hpp>
void Model::SetCollideMode( const std::string &m )
{
typedef std::map<std::string, Body* > Bodies;
struct Helper
{
static SetCollideMode(const std::pair<std::string, Body*> & value,
const std::string & m)
{
value.second->SetCollideMode(m);
}
};
for_each(bodies.begin(),
bodies.end(),
boost::bind(Helper::SetCollideMode,_1, m));
}
Просто мои 2 цента ..