访问 class 私有的大量数据的正确方法是什么?

What is the correct way to access a large amount of data that is private to a class?

我只是想知道如何用 C98 解决这个问题(所以没有 shared_ptr):

(正如 Keith M 所建议的。我没有提到我试图解决的问题)

我知道这段代码是错误的,从我的角度来看,我想解决两个主要问题:

  1. 如果对象 return 因为地图上的这个位置被删除或覆盖而消失(我会有一个指针指向任何地方,UB!)
  2. 如果 BigData 的实例 returned 被修改

当然,我想找到一个解决方案,避免将来其他人更改此代码的错误

我想到了这些解决方案:

  1. 在 BigData 中包含一个互斥体。这解决了问题 2
  2. 将函数更改为 return 一个真正的对象,而不是指针(这非常好,但缺点是在复制非常大的 class 时会降低性能)这解决这两个问题
  3. 实现我自己的 shared_ptr class(不能使用 C11 或 boost)。这解决了问题 1
  4. 在 class DataStorage 上创建锁定/解锁(真的很糟糕!)。这解决了两个问题
  5. 保持错误并多多祈祷。这个....

我很确定很多人在遗留代码中找到了这样一段代码,我想找到最好的解决方案。

P.S。我知道我正在使用 C11 互斥锁。我的真实代码没有,但是这样写示例代码更容易。

你的想法是正确的:保持数据隐私,并提供一个getter获取数据或部分数据。

您的代码中有错误。 getStuff() 的签名 return 是指向 BigData 的指针,但实现 return 是对 BigData 的引用。类型不匹配。

您是对的,因为您不想制作 BigData 的副本。所以你有三个选择:

  • return myReallyBigDatas[which_one]; // return 大数据&
  • return &myReallyBigData[which_one]; // return 大数据*
  • returnmyReallyBigDatas.find(which_one); // return map::iterator

实际上,shared_ptrmutex 是完全独立的,您可能需要两者 - shared_ptr 用于保证只释放一次资源,而 mutex 用于保证没有并发 [​​=78=] 操作(或并发读取,取决于互斥类型)。

使用shared_ptr基本上意味着没有数据的单一所有者。虽然这是可以管理的(例如引用计数),但它并不总是最好的解决方案(记住循环依赖、需要 weak_ptr 等)——有时最好找出资源的单一所有者,谁将负责在不再需要时释放它(例如,如果你有一个工作线程池,它可能是产生其他线程的线程) - 当然,你必须保证它比其他人活得更久,以使每个人都可以访问数据。所以你有几个选项来管理对象的生命周期:

  • "borrow" 来自 boost/C++11 标准 library/Loki/some-other-existing-implementation 的代码(检查许可证以验证您是否可以使用它们)而不使用整个库 - 您可能需要对它们进行更改
  • 写你自己的智能指针 - 很难而且只为专业人士 - 完全不推荐
  • select 资源的单一所有者 - 这是我推荐的

谈到访问冲突管理,基本上有两种方法:

  • 使用某种锁来管理它们(我假设你有一把锁)
  • 通过只允许一个线程写入对象来避免它们。其他通常可能想要的将不得不请求 "owner thread" 的写入。这种方法非常适合单个资源所有者,但它更像是一个参与者模型,而不是典型的共享内存多线程,因此可能很难在遗留应用程序中引入。

您可以将锁与任何树内存管理方法一起使用,单作者方法最适合单所有者。请注意,这是范例的重大变化,可能需要大量工作来实现诸如消息队列和工作人员之类的东西。

如果您已经拥有基础设施(队列、工作人员等),我建议您考虑单一所有者、单一作者方法,否则带锁的单一所有者可能是一个很好的解决方案。如果您无法 select 单一所有者,请从现有库中提取代码 - 不要自己编写,因为您会犯一些错误,多线程环境中的内存管理确实 很难

编辑 1

既然你把问题说清楚了,我觉得回答有点太高了,我再补充一些细节。

你能做的最简单的事情是 return 一个 BigData& 而不是 BigData* - 那时没有人应该删除它(当然这是可能的,但实际上一切都在 C++ 中) .否则,您还可以:

  • 仅允许单个线程使用单个 BigData 实例 -(例如通过存储额外的 std::map<int, thread_id> 以及有关已使用 BigData 的信息 - 仅当您不需要并发访问时从多个线程到同一个实例
  • return 类似 BigDataProxy 而不是 BigData - Proxy 应该有一个特殊的资源移除函数,然后由 [=77 执行=] - 这实际上只是 shared_ptr 的特例,但实现起来可能更简单。

从概念上讲,Proxy 类似于(伪代码 - 忽略 private/public 成员等):

class BigDataProxy {
  public:
  BigDataProxy(data_, instanceId_): data(data_), instanceId(instanceId_) {
    std::lock_guard l(data.mutex);
    data.interestedThreads[instanceId].insert(this_thread::thread_id);
  }

  ~BigDataProxy() {
    std::lock_guard l(data.mutex);
    data.interestedThreads[instanceId].remove(this_thread::thread_id)
    if(data.interestedThreads[instanceId].empty() && data.toDelete.contains(instanceId) {
      data.elems.remove(instanceId);
      data.toDelete.remove(instanceId);
    }
  }

  BigData& operator*() {
    return data.elems[instanceId];
  }
  void remove() {
     std::lock_guard l(data.mutex);
     data.toDelete.add(instanceId);
  }

  private: 
    DataStorage& data;
    int instanceId;
}

随着 DataStorage 的变化要求它看起来像这样:

class DataStorage {

  std::mutex mutex;
  std::map<int, BigData> elems;
  std::set<int> toDelete;
  std::map<int, std::set<thread_id> > interested_threads;
}

请注意,这是伪代码,这里的异常处理会很困难。