在 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)) {}
a
和 b
将指向同一个共享对象,并且您不会遇到段错误。
但这种方法当然不会阻止有人将 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 如果从原始指针构造)。
我想在不同的对象之间共享对象内存(例如 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)) {}
a
和 b
将指向同一个共享对象,并且您不会遇到段错误。
但这种方法当然不会阻止有人将 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 如果从原始指针构造)。