Как использовать семантику перемещения для члена std :: vector без нарушения инкапсуляции? - PullRequest
1 голос
/ 09 июня 2019

Предположим, у меня есть класс с закрытым членом std :: vector, и класс поддерживает некоторый контракт на содержимое вектора, например, сохраняет элементы отсортированными.

#include <algorithm>
#include <utility>
#include <vector>

class Foo
{
public:
  using value_type = std::pair<int, float>;

  Foo() = default;

  explicit Foo(const std::vector<value_type>& data):
    data_(data)
  {
    std::stable_sort(data_.begin(), data_.end(), [](const value_type& lhs, const value_type& rhs)
      {
        return lhs.first < rhs.first;
      });
  }

  explicit Foo(std::vector<value_type>&& data):
    data_(std::move(data))
  {
    std::stable_sort(data_.begin(), data_.end(), [](const value_type& lhs, const value_type& rhs)
      {
        return lhs.first < rhs.first;
      });
  }

  // Return the element corresponding to the given key,
  // 0 if there is no such element
  float getValue(int key) const
  {
    auto cmp = [](const value_type& element, int key)
    {
      return element.first < key;
    };
    auto it = std::lower_bound(data_.begin(), data_.end(), key, cmp);
    if (it == data_.end() || it->first != key)
    {
      return 0;
    }
    return it->second;
  }

  // Increment all keys by the specified value
  void incrementAllKeys(int value)
  {
    for (value_type& element : data_)
    {
      element.first += value;
    }
  }

  // Add an element.
  // Internal vector remains sorted.
  // O(N) complexity in the worst case (adding an element to front),
  // because data has to be moved.
  void addElement(int index, float value);

private:
  std::vector<value_type> data_;
};

Все функции-членыподдерживать контракт класса (data_ отсортирован).Однако у пользователя все еще есть способ разорвать контракт при перемещении данных в Foo:

std::vector<std::pair<int, float>> v
{
  {10, 1.4322f},
  {1, 3.223f},
  {5, 2.2323}
};
// Get a pointer to actual data
std::pair<int, float>* ptr = v.data();

Foo foo {std::move(data)}; // OK, the constructor sorts data

// Legal: ptr is not invalidated after move/sort
assert(ptr[0].first == 1);
// Break the internal state
ptr[0].first = 42;

Я хочу убедиться, что пользователь не сможет изменить Foo :: data_.Конечно, я могу просто запретить перемещение данных в Foo, но это нежелательно из-за производительности (избыточное копирование).

Эта проблема не ограничивается std :: vector: это относится к любому контейнеру, для которого указатели/ ссылки и / или итераторы не становятся недействительными после перемещения.

Я рассмотрел использование шаблона Builder, например:

class FooBuilder
{
public:
  void reserve(std::size_t capacity);
  void addElement(int key, float value);

  // Moves data into Foo.
  // This class has to be a friend of Foo;
  // alternatively, Foo can be constructible from FooBuilder and FooBuilder should have some
  //   std::vector<std::pair<int, float>> FooBuilder::release();
  // member function.
  Foo build();

private:
  std::vector<std::pair<int, float>> data_;
};

FooBuilder решает проблему экспозиции, разрешая семантику перемещения, но общедоступнуюинтерфейс очень ограничен по сравнению с std :: vector.

Есть ли лучший подход?

...