Расширение контейнера STL с помощью композиции или свободных функций? - PullRequest
0 голосов
/ 12 апреля 2011

Скажем, мне нужен новый тип в моем приложении, который состоит из std::vector<int>, расширенного одной функцией. Простым способом будет составление (из-за ограничений в наследовании контейнеров STL):

class A {
    public:
        A(std::vector<int> & vec) : vec_(vec) {}
        int hash();
    private:
        std::vector<int> vec_
}

Это требует, чтобы пользователь сначала создал vector<int> и копию в конструкторе, что плохо, когда мы собираемся обработать значительное количество больших векторов. Можно, конечно, написать пропуск к push_back(), но это вводит изменчивое состояние, которого я хотел бы избежать.

Так что мне кажется, что мы можем либо избегать копий, либо сохранять неизменность, это правильно?

Если это так, самый простой (и эквивалентный по эффективности) способ - использовать typedef и свободные функции в области имен:

namespace N {
typedef std::vector<int> A;
int a_hash(const A & a);
}

Это как-то неправильно, так как расширения в будущем будут "загрязнять" пространство имен. Также возможен вызов a_hash(...) для любого vector<int>, что может привести к неожиданным результатам (при условии, что мы наложим ограничения на A, которым должен следовать пользователь или которые в противном случае были бы применены в первом примере)

Мои два вопроса:

  • как можно не жертвовать неизменностью и эффективностью при использовании приведенного выше кода класса?
  • когда имеет смысл использовать свободные функции в отличие от инкапсуляции в классах / структурах?

Спасибо!

Ответы [ 3 ]

6 голосов
/ 12 апреля 2011

Хеширование - это алгоритм, а не тип, и, вероятно, его не следует ограничивать данными любого конкретного типа контейнера.Если вы хотите обеспечить хеширование, возможно, имеет смысл создать функтор, который вычисляет хэш по одному элементу (int, как вы уже написали выше) за раз, а затем используйте std::accumulate или std::for_each дляпримените это к коллекции:

namespace whatever { 
struct hasher { 
    int current_hash;
public:
    hasher() : current_hash(0x1234) {}

    // incredibly simplistic hash: just XOR the values together.
    operator()(int new_val) { current_hash ^= new_val; }
    operator int() { return current_hash; }
};
}

int hash = std::for_each(coll.begin(), coll.end(), whatever::hasher());

Обратите внимание, что это позволяет coll быть vector или deque, или вы можете использовать пару istream_iterators для хеширования данных в файле...

0 голосов
/ 12 апреля 2011

Ad immutable: Вы можете использовать конструктор диапазона вектора и создать входной итератор для предоставления содержимого для вектора. Конструктор диапазона просто:

template <typename I>
A::A(I const &begin, I const &end) : vec_(begin, end) {}

Генератор немного сложнее. Если у вас теперь есть цикл, который создает вектор с использованием push_back, потребуется немало переписать для преобразования в объект, который возвращает один элемент за раз из метода. Чем вам нужно обернуть ссылку на него в допустимом итераторе ввода.

Функции без рекламы: Из-за перегрузки загрязнение пространства имен обычно не является проблемой, поскольку символ будет рассматриваться только для вызова с определенным типом аргумента.

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

#include <vector>
namespace std {
    int hash(vector<int> const &vec) { /*...*/ }
}
//...
std::vector<int> v;
//...
hash(v);

Теперь вы все равно можете назвать hash неквалифицированным, но не видите его для каких-либо других целей, если вы не сделаете using namespace std (лично я почти никогда не делаю этого и просто использую префикс std:: или делаю using std::vector чтобы получить только символ, который я хочу). К сожалению, я не уверен, как поиск, зависящий от пространства имен, работает с typedef в другом пространстве имен.

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

0 голосов
/ 12 апреля 2011

Одним из простых решений является объявление закрытой переменной-члена как ссылки и инициализация в конструкторе. Этот подход вводит некоторые ограничения, но в большинстве случаев является хорошей альтернативой.

class A {
    public:
        A(std::vector<int> & vec) : vec_(vec) {}
        int hash();
    private:
        std::vector<int> &vec_; // 'vec_' now a reference, so will be same scoped as 'vec'
};
...