如何设计两个紧密相关的数据结构的所有权?

时间:2014-03-16 13:39:44

标签: c++ oop pointers reference ownership

我有一个类A和一个结构B(它是一个普通的旧数据结构)。这两个模型以A在某种意义上表示整个系统的状态的方式对系统进行建模,并且由多个B和其他状态组成。这意味着B在没有A的情况下可能毫无价值。我想制作一个API,您可以在其中添加" BA,然后直接修改B s的状态(通过保留类似对它们的引用)。

但我不确定的是如何根据所有权来设计这个。我有几个选择。由于我使用的是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系统自己创建对象。这可能是更好的方法,因为B没有A就没有了。我甚至可能以某种方式使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时,它可能会在内存中移动,从而使返回的引用无效。你会如何解决这个问题?或者你会尝试以其他方式设计这个?

1 个答案:

答案 0 :(得分:0)

没有理由不能在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); }

shared_ptr

在这个实现中,我们持有每个B实例的共享指针。共享指针使用类似于Java处理内存管理的引用计数器。只要某人拥有对B的特定实例的引用(通过shared_ptr),该特定实例将保留在内存中。 shared_ptr与Java的不同之处在于 引用计数decriments为0时,实例将被删除。 Java等待将来垃圾收集的某些方面。这种模式在C ++中称为RAII(资源获取是初始化)。

您的第二个模型可以类似地处理。您可以直接在向量或原始指针中使用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; }; ,我将在这里显示原始指针:

B

在这个设计中,由于我们使用了原始指针,我们必须自己管理内存。这引入了对析构函数的需求,该析构函数在A的实例被销毁时释放存储在向量中的所有B实例。我们现在还返回指向我们在create中创建的每个新B实例的指针。这个内存模型比第一种方法更模糊,因为我们已经有了原始指针,因此需要采用一种策略,这种策略只能通过纪律强制执行,关于谁拥有并负责每个内存管理。 B的实例。针对此API编码的开发人员可以删除返回给他的B实例,这会在您下次尝试访问shared_ptr的实例时导致段错误或未定义的行为。

我的推荐?使用{{1}}除非这需要快速性能并且每次操作都很重要。

修改

按照Yakk的建议隐藏复制构造函数和赋值运算符。