Этот вопрос свободно основан на библиотеке Boost.Graph (BGL), которая использует посетитель-как шаблон для настройки рекурсивных (поисковых) алгоритмов. BGL передает объекты посетителя по значению (по аналогии с объектами функции STL) и документация состояния
Так как параметр посетителя передается по значению, если ваш посетитель содержит состояние, то любые изменения состояния в течение алгоритма будут внесены в копию объекта посетителя, а не переданного объекта посетителя. Поэтому вы можете захотеть, чтобы посетитель удерживал это состояние указателем или ссылкой.
Мой вопрос: каков наилучший способ реализации ссылочной семантики классов посетителей с отслеживанием состояния? Абстрагируясь от классов точных указателей (необработанные или уникальные против общих, константные или неконстантные), что было бы лучшим местом для размещения ссылки: в передаче параметров или в элементе данных?
Альтернатива 1: посетитель хранит состояние указателем и передается по значению (как в Boost.Graph)
class Visitor
{
public:
Visitor(): state_(new State()) {}
void start() { /* bla */ }
void finish() { /* mwa */ }
private:
State* state_;
}
template<typename Node, typename Visitor>
int algorithm(Node const& n, Visitor v)
{
v.start();
algorithm(next(n), v);
v.finish();
}
Альтернатива 2: посетитель содержит данные по значению и передается по указателю
class Visitor
{
public:
Visitor(): state_() {}
void start() { /* bla */ }
void finish() { /* mwa */ }
private:
State state_;
}
template<typename Node, typename Visitor>
int algorithm(Node const& n, Visitor* v)
{
v->start();
algorithm(next(n), v);
v->finish();
}
Моя текущая склонность: Я нахожу Альтернативу 1 [передачу по значению объектов, которые содержат указатели / ссылки], немного неудобной, потому что посетитель не удовлетворяет семантике значений, поэтому я бы предпочел сделать семантику ссылок понятной в списке параметров [Альтернатива 2]. Есть ли какие-либо другие соображения или альтернативы, которые актуальны здесь?
Я понимаю ваш дискомфорт по поводу Альтернативы 1, но я думаю, что это тот случай, когда «этот автобус ушел»; Другими словами, направление стандартной библиотеки C ++ (и Boost, а не только BGL) благоприятствует использованию шаблона «хранимой ссылки».
Рассмотрим, например, повсеместное использование функторов, которые можно реализовать с помощью лямбда-выражений. Насколько я знаю, все интерфейсы стандартной библиотеки (и boost) передают аргументы функтора по значению, поэтому, если функтор хранит состояние, он должен хранить его по ссылке. Следовательно, мы должны привыкнуть к [&](){}
скорее, чем [=](){}
, И, по аналогии, мы должны привыкнуть к тому, что посетители держат ссылки (или указатели, но я предпочитаю ссылки) на их состояние.
На самом деле есть веская причина передавать функторы (и посетителей) по значению, а не по ссылке. Если они были переданы по ссылке, они должны быть переданы либо const&
что сделало бы невозможным изменение состояния или &
, что сделало бы невозможным использование временных значений. Единственной другой возможностью будет передача явного указателя, но его нельзя использовать с лямбдами или временными значениями (если временные значения не были излишне выделены в куче).
Есть третий вариант:
class Visitor
{
public:
Visitor(): state_() {}
void start() { /* bla */ }
void finish() { /* mwa */ }
private:
State state_;
};
template<typename Node, typename Visitor>
int algorithm(Node const& n, Visitor v)
{
v.start();
algorithm(next(n), v);
v.finish();
}
// Set the reference semantics here, use value everywhere else
algorithm(myNode, boost::ref(myVisitor)); // ... or std::ref in c++11
Я думаю, что это обычно одобряется стандартом, в отличие от явной маркировки чего-либо как указателя или ссылки. В конце концов, std::ref
а также std::cref
были введены для решения этой проблемы.
С другой стороны, в книге «Стандарты кодирования C ++» Саттер и Александреску утверждают, что функторы всегда должны легко и быстро копироваться. Они рекомендуют использовать внутренний блок с подсчетом ссылок (IIRC, здесь нет книги). Так что пока std::ref
или же std::cref
решит вашу проблему, они чаще используются для «адаптации» не функторных объектов, например при прохождении std::vector
через std::bind
,
Альтернатива 1, с shared_ptr<T>
(или еще лучше: shared_ptr<T const>
), вероятно, ваш лучший вариант. В любом случае вы просто «оборачиваете» семантику указателя за семантикой значения для кода BGL, что нормально, если вы правильно настроили время жизни объекта.
Бессмысленно пытаться сделать вашего посетителя без гражданства, когда на самом деле у него есть состояние. Я не вижу никаких проблем с (2).