不变数据模型的内存管理

时间:2019-07-17 15:10:58

标签: c++

我已经编写了一个玩具数据模型,该模型将std::set用于唯一的实例。可以将其称为flyweight模式或hash-consing(尽管从技术上讲,我还没有使用哈希表)。

#include <set>
#include <cassert>

enum Color { Red, Green, Blue };

struct Shape {

  // Set the color, return a new shape.
  virtual const Shape* color(Color) const = 0;

};

template<class T>
const T* make_shape(T value) {

  struct cmp {
    bool operator()(const T* a, const T* b) const {
      return *a < *b;
    }
  };

  static std::set<const T*, cmp > values;

  auto iter = values.find(&value);

  if(iter != values.end()) {
    return *iter;
  }

  auto p = new T(value);
  values.insert(p);
  return p;

}

struct Circle : public Shape {
  Color c = Red;
  virtual const Shape* color(Color c) const {
    Circle r;
    r.c = c;
    return make_shape(r);
  }
  bool operator<(const Circle& rhs) const { return c < rhs.c; }
};

这是我的测试代码。请注意,前两行如何返回相同的指针,因此这些语义与通过newmake_shared进行的常规分配不同。


void test_shape() {

  auto s0 = make_shape(Circle{});
  auto s1 = make_shape(Circle{});

  // Structurally equivalent values yield the same pointer.
  assert(s0 == s1);

  // Color is red by default, so we should get the same pointer.
  auto s2 = s0->color(Red);

  assert(s2 == s0);

  // Changing to Green gives us a different pointer.
  auto s3 = s0->color(Green);

  assert(s3 != s0);

  printf("done\n");

}

int main() {
    test_shape();
}

现在,形状已被泄漏。也就是说,一旦该数据模型的客户端不再具有指向Shape的指针,就不会释放该形状(将set视为弱引用,应予以破坏)。

因此,我想使用shared_ptr来管理我的对象,因为它看起来很简单(也可以接受其他想法,但是我不想添加依赖项,例如boost )。

但是我在shared_ptr上遇到了一些麻烦。我尝试通过使用std::set进行比较来更新std::weak_ptr<const T>来存储owner_before

我需要一个shared_ptr来查找集合中的对象。但这需要更新一个对象,这里的部分重点是能够快速获得一个现有的结构上相等的对象。

更新

我还尝试将set保留为原始指针,并使用shared_ptr删除器删除元素。 las,这要求我使用似乎无法接受的shared_from_this,尽管我不确定为什么:

shape.cpp:30:16: error: member reference base type 'Circle *const' is not a
      structure or union
    return iter->shared_from_this();
           ~~~~^ ~~~~~~~~~~~~~~~~

2 个答案:

答案 0 :(得分:1)

一个替代解决方案是让您的客户使用拥有其分发的对象的工厂。这样,您的客户可以使用普通指针来引用对象。

一旦客户端完成,它可以与所有对象一起处置工厂。

此外,可能需要对工厂参考进行计数,或者对其保留shared_ptr

答案 1 :(得分:0)

这是完整的代码。事实证明,我只是不太擅长编程,并且没有意识到我必须取消对某些内容的引用。

基本方法是shared_ptr删除程序从set中删除原始指针。

#include <set>
#include <cassert>
#include <memory>

enum Color { Red, Green, Blue };

struct Shape;
struct Circle;

struct Shape : public std::enable_shared_from_this<Shape> {

    virtual ~Shape() = default;

    // Set the color, return a new shape.
    virtual std::shared_ptr<Shape> color(Color) const = 0;

};

template<class T>
std::shared_ptr<Shape> make_shape(T value) {

    struct cmp {
        bool operator()(const T* a, const T* b) const {
            return *a < *b;
        }
    };

    static std::set<T*, cmp> values;

    auto iter = values.find(&value);

    if(iter != values.end()) {
        return (*iter)->shared_from_this();
    }

    auto ptr = std::shared_ptr<T>(new T(value), [](T* p) {
        printf("deleting %p\n", (void*)p);
        values.erase(p); delete p;
    });
    values.insert(ptr.get());
    return ptr;

}

struct CircleCount {
    static int count;

    CircleCount() { ++count; }
    CircleCount(const CircleCount&) { ++count; }
    ~CircleCount() { --count; }
};

int CircleCount::count;

struct Circle : public Shape {
    CircleCount count;
    Color c = Red;
    virtual std::shared_ptr<Shape> color(Color c) const {
        Circle r;
        r.c = c;
        return make_shape(r);
    }
    bool operator<(const Circle& rhs) const { return c < rhs.c; }
};

void test_shape() {

    {
        auto s0 = make_shape(Circle{});
        auto s1 = make_shape(Circle{});

        assert(s0 == s1);

        auto s2 = s0->color(Red);

        assert(s2 == s0);

        auto s3 = s0->color(Green);

        assert(s3 != s0);
    }

    // All circles should be cleaned up.
    printf("circles: %d\n", CircleCount::count);
    assert(CircleCount::count == 0);

    printf("done\n");

}

int main() {
    test_shape();
}


更新

我将其转变为通用名称,可以进行代码审查here