C++ 如何在不破坏封装的情况下为std::vector成员使用移动语义?

C++ 如何在不破坏封装的情况下为std::vector成员使用移动语义?,c++,vector,move,encapsulation,C++,Vector,Move,Encapsulation,假设我有一个带有私有std::vector成员的类,该类对向量的内容保持某种约定,例如,保持元素排序 #include <algorithm> #include <utility> #include <vector> class Foo { public: using value_type = std::pair<int, float>; Foo() = default; explicit Foo(const std::vecto

假设我有一个带有私有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_;
};
#包括
#包括
#包括
福班
{
公众:
使用value_type=std::pair;
Foo()=默认值;
显式Foo(const std::vector&data):
数据(数据)
{
std::稳定排序(数据开始(),数据结束(),[](常量值类型和lhs,常量值类型和rhs)
{
返回lhs.firstfirst!=key)
{
返回0;
}
返回->秒;
}
//按指定值递增所有键
void incrementAllKeys(int值)
{
对于(值类型和元素:数据)
{
element.first+=值;
}
}
//添加一个元素。
//内部向量保持排序。
//O(N)最坏情况下的复杂性(在前端添加一个元素),
//因为数据必须移动。
无效加法(整数索引、浮点值);
私人:
std::矢量数据;
};
所有成员函数都维护类协定(数据已排序)。但是,用户仍然有办法 将数据移动到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;
std::vector v { {10,1.4322f}, {1,3.223f}, {5, 2.2323} }; //获取指向实际数据的指针 std::pair*ptr=v.data(); Foo Foo{std::move(data)};//好的,构造函数对数据进行排序 //合法:移动/排序后ptr不会失效 断言(ptr[0]。first==1); //打破内部状态 ptr[0].first=42; 我想确保用户不能修改Foo::data\ux。当然,我可以简单地禁止将数据移动到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 { 公众: 空隙储备(标准:尺寸和容量); 无效加法(整数键、浮点值); //将数据移动到Foo中。 //这个班必须是福的朋友; //或者,可以从FooBuilder构建Foo,FooBuilder应该有一些 //std::vector FooBuilder::release(); //成员函数。 Foo build(); 私人: std::矢量数据; }; FooBuilder解决了暴露问题,同时允许移动语义,但与std::vector相比,它的公共接口非常有限


有更好的方法吗?

如果类的用户想要破坏封装,这是他们的责任,如果出现问题,这是他们的问题。您很少能够100%保护自己。您只需在te文档中指定指针和引用可能会失效。(或者干脆不保证他们不是)。从那时起,如果用户接触移入的数据,那就是UB和他们的责任。这就是为什么
map
将键类型声明为
const
@nicolabolas,但在这种情况下没有帮助。如果类的用户想要破坏封装,这是他们的责任,如果出现问题,这是他们的问题。您很少能够100%保护自己。您只需在te文档中指定指针和引用可能会失效。(或者干脆不保证他们不是)。从那时起,如果用户接触移动的数据,则是UB及其责任。这就是为什么
map
将键类型声明为
const
@nicolabolas的原因,但在这种情况下,它不会有帮助。