在 shared_ptr 上分享 class 记忆

Share class memory over shared_ptr

我想在不同的对象之间共享对象内存(例如 Reader/Writer 访问同一个内存池)。它运行良好,但我无法共享一个 shared_ptr。

struct A {
  A() {}

  A(const A &other) {
    i = other.i;
  }

  std::shared_ptr<int> i;
};

struct B : public A {
  B(const A &other) : A(other) {}
};

我想让第二个示例运行,但它抛出异常。因为变量 i 没有初始化并且 shared_ptr 没有被复制(他是空的)。

{ // don´t throw
    A a;
    a.i = std::make_shared<int>(10);

    B b(a);
    *b.i = 11;

    printf("%d\n", *a.i);
}  
{ // throw
    A a;
    B b(a);

    b.i = std::make_shared<int>(10);
    printf("%d\n", *a.i);
}

只有B应该初始化变量i。

下面是一个解决方案,但我真的需要另一个包装器吗class?

struct A {
  A() : i(std::make_shared<std::vector<std::shared_ptr<int>>>()) {}

  A(const A &other) {
    i = other.i;
  }

  std::shared_ptr<std::vector<std::shared_ptr<int>>> i;
};

struct B : public A {
  B(const A &other) : A(other) {}
};

int main(int argc, char *argv[]) {

  { // throw
    A a;
    B b(a);

    b.i->emplace_back(std::make_shared<int>(10));
    printf("%d\n", *a.i->at(0));
  }
}

另一个例子就是使用原始指针,但我想问你,它如何与 shared_ptr 一起工作。

int 类型只是一个例子。如果没有默认构造函数,它也可能是一个沉重的 class。

你的第一个场景

您的代码抛出错误,因为:

  • 当您创建 a 时,a.i 是空的 shared_ptr
  • 然后你用构造函数复制a创建b。所以 b.i 是空的 shared_ptr
  • 然后您将指向新创建对象的共享指针分配给 b.i。但这并没有改变 a.i 指针,它仍然是空的。
  • 最后您尝试取消引用 a.i。但是由于 a.i 是空的,即使用计数为 0 且没有有效指针,因此它是未定义的行为(可能会发生段错误)。

第一个场景的改进:

您可以通过定义 A 的默认构造函数轻松避免此陷阱:

    A() : i(std::make_shared<int>(0)) {}

ab 将指向同一个共享对象,并且您不会遇到段错误。

但这种方法当然不会阻止有人将 b.i 重新分配给另一个共享指针。这就是 struct 的问题:你把房子的钥匙交给你,收拾残局就得靠你了。

改进的变体可以是完全封装的 class,其中 i 将受到保护,函数或操作员可以访问 i。我选择了一种重载赋值形式 int 和转换为 int 的方式,以允许直观的使用,但这是一个品味问题:

class A {
public:
    A() : i(std::make_shared<int>(0)) {}
    A(const A &other) { i = other.i; }
    operator int() { return *i; }   // you can use A when you could use an int
    A& operator= (int x) {
        *i = x;
        return *this;
    }
    // todo:  copy assigment:  take the pointer or take the value ? 
protected:
    std::shared_ptr<int> i;
};
struct B : public A {
    B(const A &other) : A(other) {}
    B& operator= (int x) {
        *i = x;
        return *this;
    }
    // todo:  copy assigment:  take the pointer or take the value ? 
}; 

这个class的用法是:

{ // don´t throw
    A a;
    a = 10;
    B b(a);
    b = 11;
    printf("%d\n", (int)a);
}
{ // don't throw either
    A a;
    B b(a);
    a = 1; 
    cout << a <<endl; 
    cout << b << endl;
    b = 10; 
    printf("%d\n", (int)a);  // to make sure that only the int value is passed
}   

你的第二种情况

在这种情况下,您已更改为使用指向共享指针向量的共享指针。

我看不出这段代码有什么问题,也没有抛出任何问题:see online demo

你的其他想法

您当然可以使用原始指针,前提是它们已正确分配给 new。

int *pi = new int(1); 
shared_ptr<int> spi(pi); 

但是注意:一旦你这样做了,shared_ptr 就拥有了所有权。这意味着 shared_ptr 负责对象的销毁。

如果你想在另一个 shared_ptr 中重用这个原始指针(或者更糟:如果它是从 shared_ptr 获得的),你的编译器不会抱怨,但你会得到未定义的行为在运行时,因为当第二个 shared_ptr 会尝试销毁一个已经被第一个 shared_ptr 销毁的对象(shared_ptr 不会意识到其他 shared_ptr 如果从原始指针构造)。