应该如何设计两个紧密关联的数据结构的所有权
How should ownership for two closely tied data structures be designed?
我有一个类A
和一个结构B
(这是一个普通的旧数据结构(。这两者以A
的方式对系统进行建模,从某种意义上说,它代表了整个系统的状态,并且由多个B
和其他状态组成。这意味着没有A
B
可以说一文不值。我想制作一个 API,您可以在其中将B
"添加"到A
,然后直接修改B
的状态(通过保留对它们的引用之类的内容(。
不过,我不确定的是,如何从所有权的角度设计它。我有几个选择。由于我正在使用C++,这很复杂。如果我使用Java,我可能会像这样设计代码:
class A {
private List<B> bs = new ArrayList<>();
public void add(B b) {
bs.add(b);
}
}
API 的用户自行创建B
,或如下所示:
class A {
private List<B> bs = new ArrayList<>();
public B create(int data) {
B b = new B(data);
bs.add(b);
return b;
}
}
A
系统自行创建对象的位置。这可能是更好的方法,因为没有A
B
就什么都不是。我甚至可以以某种方式使B
构造函数仅供A
访问,但我不确定如何。
但请记住,这是在Java中。该系统将在C++中编程,并且它的工作方式不同。
我想第一个示例看起来很相似:
class A {
public:
void add(B& b)
{
bs.push_back(&b);
}
private:
std::vector<B*> bs;
};
但由于我更喜欢第二个选项,我尝试翻译它:
class A {
public:
B& create(int data)
{
bs.emplace_back(data);
return bs.back();
}
private:
std::vector<B> bs;
};
但在这种情况下,我返回对B
的引用.调整bs
大小时,它可能会在内存中移动,从而使返回的引用无效。你会如何解决这个问题?或者你会尝试以什么其他方式设计它?
理由不能在C++中使用任一数据模型,只是语法不同。
在第一种情况下,add
的调用方必须实例化 B
的实例。 然后它将 B 传递到 A
的实例中,然后 持有指向 b
的引用(或指针,C++术语(:
#include <memory>
#include <vector>
class B {
};
class A {
public:
void add(std::shared_ptr<B> b) {
bs.push_back(b);
}
private:
std::vector<std::shared_ptr<B> > bs;
};
int main(int argc, const char *argv[]) {
A a;
std::shared_ptr<B> b(new B);
a.add(b);
}
在此实现中,我们持有指向 B 的每个实例的共享指针。 共享指针使用类似于 Java 处理内存管理的引用计数器。 只要有人持有对 B 的特定实例的引用(通过 shared_ptr
(,该特定实例将保留在内存中。 shared_ptr
与 Java 的不同之处在于,一旦引用计数为 0,实例就会被删除。 Java等待将来的某个时间点进行垃圾回收。 此模式在 C++ 中称为 RAII(资源获取是初始化(。
您的第二个模型可以类似地处理。 您可以在向量中使用 shared_ptr
s,也可以直接使用原始指针。 由于我已经在上面展示了shared_ptr
,我将在这里显示原始指针:
class B {};
class A {
public:
~A() {
for (std::vector<B*>::iterator it = bs.begin(); it != bs.end(); ++it) {
delete *it;
}
bs.clear();
}
B* create(int data) {
B *b = new B(data);
bs.push_back(b);
return b;
}
private:
// block copy construction since we don't manually copy the contents of our
// vector
A(const A&) {}
void operator=(const A&) {}
std::vector<B*> bs;
};
在这个设计中,由于我们使用了原始指针,我们必须自己管理内存。 这就引入了对析构函数的需求,该析构函数在销毁A
实例时释放存储在向量中的所有B
实例。 我们现在还返回一个指针,指向我们在 create
中创建的每个新B
实例。 这种内存模型比第一种方法更加模糊,因为我们有原始指针,因此需要采用一个策略,该策略只能通过纪律强制执行,关于谁拥有并负责每个B
实例的内存管理。 针对此 API 编码的开发人员可以删除返回给他的 B 实例,这将导致下次尝试访问该 B
实例时出现段错误或未定义的行为。
我的建议?使用shared_ptr
,除非这需要快速出血性能并且每个操作都很重要。
编辑
根据 Yakk 的建议隐藏复制构造函数和赋值运算符。
- 如何在C++中从两个单独的for循环中添加两个数组
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 当在同一名称空间中有两个具有相同签名的函数时,会发生什么
- 如何返回一个类的两个对象相加的结果
- 如何在C++中将一个无符号的 int 转换为两个无符号的短裤?
- 如何将两个不同矢量的同一位置的两个元素组合在一起
- 两个字符串在 c++ 中不相等
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 两个文件使用彼此的功能-如何解决
- 为什么Mat类的两个对象可以在不重载运算符+的情况下添加
- 使用聚合创建和关联两个不同的对象成员
- 使用指针将两个对象(每个都与一个类)相关联
- 如何在C++中关联两个矩阵?
- 具有两个搜索条件的C++关联容器
- 将两个变量关联起来一起更改
- 来自两个不同文件的关联值
- 正在搜索交换两个关联容器的无锁可能性
- 当两个模板的类型相关联时,如何使用策略模板
- 两个类之间的双向关联
- 应该如何设计两个紧密关联的数据结构的所有权