我应该在 class 构造函数内部还是外部初始化 shared_ptr ?
Should I initialize a shared_ptr inside or outside the class constructor?
给定以下示例代码:
#include <memory>
class Foo {
public:
Foo(std::shared_ptr<int> p);
private:
std::shared_ptr<int> ptr;
};
Foo::Foo(std::shared_ptr<int> p) : ptr(std::move(p)) {
}
class Bar {
public:
Bar(int &p);
private:
std::shared_ptr<int> ptr;
};
Bar::Bar(int &p) : ptr(std::make_shared<int>(p)) {
}
int main() {
Foo foo(std::make_shared<int>(int(256)));
Bar bar(*new int(512));
return 0;
}
Foo 和 Bar 都可以正常工作。但是,在调用构造函数时创建 shared_ptr 然后使用 std::move 转移所有权和仅传递对对象的引用并将 shared_ptr 的创建委托给 class 构造函数?
我认为第二种方法更好,因为我不必移动指针。但是,我在阅读的代码中看到的大多是第一种方式。
我应该使用哪一个,为什么?
Foo 是正确的。
酒吧令人厌恶。它涉及内存泄漏、不安全的异常行为和不必要的复制。
编辑:内存泄漏的解释。
解构这一行:
Bar bar(*new int(512));
这些操作的结果:
- 调用 new int(512),调用 operator new,返回指向堆上 int 的指针(内存分配)。
- 解引用指针以便为 Bar 的构造函数提供常量引用
- Bar 然后用 make_shared 返回的一个构建它的 shared_ptr(这部分是有效的)。这个 shared_ptr 的 int 是用引用传递的 int 的副本初始化的。
- 然后是函数returns,但是由于没有变量记录new返回的指针,所以没有什么可以释放内存。为了销毁对象并释放其内存,每个新的都必须通过删除进行镜像。
- 因此内存泄漏
这取决于你想要达到什么。
如果你在内部需要一个 shared_ptr
,因为你想与你稍后创建的其他一些对象共享这个对象,第二种方法可能更好(除了那个可怕的构造函数调用显然)。
如果您想要共享现有对象的所有权(这是更常见的情况,真的),您别无选择,您需要使用第一种方式。
如果这些都不适用,您可能一开始就不需要 shared_ptr
。
第二种方式不正确;它泄漏内存。有
Bar::Bar(int &p) : ptr(std::make_shared<int>(p)) {
}
....
Bar bar(*new int(512));
std::make_shared
做的shared_ptr
取p
的值(也就是512
)构建一个新的共享指针,负责新的一块内存;它不对 p
所在的内存地址负责。这块——你在 main
中分配的那块——然后就丢失了。这段特殊的代码适用于
// +---------------------- building a shared_ptr directly
// v v----- from p's address
Bar::Bar(int &p) : ptr(std::shared_ptr<int>(&p)) {
...但是看看那个。 那是纯粹的邪恶。没有人期望构造函数的引用参数要求它引用新对象将负责的堆对象。
你可以更理智地写
Bar::Bar(int *p) : ptr(p) {
}
....
Bar bar(new int(512));
事实上,如果您愿意,您可以为 Foo
提供第二个构造函数来执行此操作。函数签名使指针必须指向堆分配的对象有多清晰,这有点争论,但 std::shared_ptr
提供了相同的功能,因此有先例。这是否是个好主意取决于您的 class 做什么。
两者都可以正常工作,但是您在main
中使用它们的方式不一致。
当我看到构造函数获取引用时(如 Bar(int& p)
),我希望 Bar 持有引用。当我看到 Bar(const int& p)
时,我希望它能保存一份副本。
当我看到一个右值引用(不是通用的,比如 Bar(int&& p)
我希望 p 在通过后不是 "surviving its content"。(好吧......对于一个没有那么有意义的......)。
在任何情况下 p
持有 int
,而不是 pointer
,并且 make_shared 期望的是转发到 int 构造函数的参数(即 . .. 一个整数,而不是整数 *)。
你的主所以必须是
Foo foo(std::make_shared<int>(int(256)));
Bar bar(512);
这将使 bar 保存值 512
的动态分配的可共享副本。
如果你这样做 Bar bar(*new int(512))
你会创建 bar 来保存你的 "new int" 的副本,它的指针被丢弃,因此 int 本身泄漏。
一般来说,像 *new something
这样的表达应该读作 "no no no no no ..."
但是你的 Bar
构造函数有一个问题:要能够接受常量或表达式返回值,它必须接受 const int&
,而不是 int&
如果您打算独占堆分配对象的所有权,我建议您接受 std::unique_ptr
。它清楚地记录了意图,如果需要,您可以在内部从 std::unique_ptr
创建一个 std::shared_ptr
:
#include <memory>
class Foo {
public:
Foo(std::unique_ptr<int> p);
private:
std::shared_ptr<int> ptr;
};
Foo::Foo(std::unique_ptr<int> p) : ptr(std::move(p)) {
}
int main() {
Foo foo(std::make_unique<int>(512));
}
不要这样做 Bar
,它很容易出错并且无法描述您的意图(如果我正确理解您的意图)。
假设您使用 int
只是作为示例,并且有一个真实的资源,那么这取决于您想要实现什么。
第一种是典型的依赖注入,通过构造函数注入对象。好的一面是可以简化单元测试。
第二种情况只是在构造函数中创建对象,并使用通过构造函数传递的值对其进行初始化。
顺便说一下,请注意您的初始化方式。这 :
Bar bar(*new int(512));
导致内存泄漏。已分配内存,但从未解除分配。
给定以下示例代码:
#include <memory>
class Foo {
public:
Foo(std::shared_ptr<int> p);
private:
std::shared_ptr<int> ptr;
};
Foo::Foo(std::shared_ptr<int> p) : ptr(std::move(p)) {
}
class Bar {
public:
Bar(int &p);
private:
std::shared_ptr<int> ptr;
};
Bar::Bar(int &p) : ptr(std::make_shared<int>(p)) {
}
int main() {
Foo foo(std::make_shared<int>(int(256)));
Bar bar(*new int(512));
return 0;
}
Foo 和 Bar 都可以正常工作。但是,在调用构造函数时创建 shared_ptr 然后使用 std::move 转移所有权和仅传递对对象的引用并将 shared_ptr 的创建委托给 class 构造函数?
我认为第二种方法更好,因为我不必移动指针。但是,我在阅读的代码中看到的大多是第一种方式。
我应该使用哪一个,为什么?
Foo 是正确的。
酒吧令人厌恶。它涉及内存泄漏、不安全的异常行为和不必要的复制。
编辑:内存泄漏的解释。
解构这一行:
Bar bar(*new int(512));
这些操作的结果:
- 调用 new int(512),调用 operator new,返回指向堆上 int 的指针(内存分配)。
- 解引用指针以便为 Bar 的构造函数提供常量引用
- Bar 然后用 make_shared 返回的一个构建它的 shared_ptr(这部分是有效的)。这个 shared_ptr 的 int 是用引用传递的 int 的副本初始化的。
- 然后是函数returns,但是由于没有变量记录new返回的指针,所以没有什么可以释放内存。为了销毁对象并释放其内存,每个新的都必须通过删除进行镜像。
- 因此内存泄漏
这取决于你想要达到什么。
如果你在内部需要一个 shared_ptr
,因为你想与你稍后创建的其他一些对象共享这个对象,第二种方法可能更好(除了那个可怕的构造函数调用显然)。
如果您想要共享现有对象的所有权(这是更常见的情况,真的),您别无选择,您需要使用第一种方式。
如果这些都不适用,您可能一开始就不需要 shared_ptr
。
第二种方式不正确;它泄漏内存。有
Bar::Bar(int &p) : ptr(std::make_shared<int>(p)) {
}
....
Bar bar(*new int(512));
std::make_shared
做的shared_ptr
取p
的值(也就是512
)构建一个新的共享指针,负责新的一块内存;它不对 p
所在的内存地址负责。这块——你在 main
中分配的那块——然后就丢失了。这段特殊的代码适用于
// +---------------------- building a shared_ptr directly
// v v----- from p's address
Bar::Bar(int &p) : ptr(std::shared_ptr<int>(&p)) {
...但是看看那个。 那是纯粹的邪恶。没有人期望构造函数的引用参数要求它引用新对象将负责的堆对象。
你可以更理智地写
Bar::Bar(int *p) : ptr(p) {
}
....
Bar bar(new int(512));
事实上,如果您愿意,您可以为 Foo
提供第二个构造函数来执行此操作。函数签名使指针必须指向堆分配的对象有多清晰,这有点争论,但 std::shared_ptr
提供了相同的功能,因此有先例。这是否是个好主意取决于您的 class 做什么。
两者都可以正常工作,但是您在main
中使用它们的方式不一致。
当我看到构造函数获取引用时(如 Bar(int& p)
),我希望 Bar 持有引用。当我看到 Bar(const int& p)
时,我希望它能保存一份副本。
当我看到一个右值引用(不是通用的,比如 Bar(int&& p)
我希望 p 在通过后不是 "surviving its content"。(好吧......对于一个没有那么有意义的......)。
在任何情况下 p
持有 int
,而不是 pointer
,并且 make_shared 期望的是转发到 int 构造函数的参数(即 . .. 一个整数,而不是整数 *)。
你的主所以必须是
Foo foo(std::make_shared<int>(int(256)));
Bar bar(512);
这将使 bar 保存值 512
的动态分配的可共享副本。
如果你这样做 Bar bar(*new int(512))
你会创建 bar 来保存你的 "new int" 的副本,它的指针被丢弃,因此 int 本身泄漏。
一般来说,像 *new something
这样的表达应该读作 "no no no no no ..."
但是你的 Bar
构造函数有一个问题:要能够接受常量或表达式返回值,它必须接受 const int&
,而不是 int&
如果您打算独占堆分配对象的所有权,我建议您接受 std::unique_ptr
。它清楚地记录了意图,如果需要,您可以在内部从 std::unique_ptr
创建一个 std::shared_ptr
:
#include <memory>
class Foo {
public:
Foo(std::unique_ptr<int> p);
private:
std::shared_ptr<int> ptr;
};
Foo::Foo(std::unique_ptr<int> p) : ptr(std::move(p)) {
}
int main() {
Foo foo(std::make_unique<int>(512));
}
不要这样做 Bar
,它很容易出错并且无法描述您的意图(如果我正确理解您的意图)。
假设您使用 int
只是作为示例,并且有一个真实的资源,那么这取决于您想要实现什么。
第一种是典型的依赖注入,通过构造函数注入对象。好的一面是可以简化单元测试。
第二种情况只是在构造函数中创建对象,并使用通过构造函数传递的值对其进行初始化。
顺便说一下,请注意您的初始化方式。这 :
Bar bar(*new int(512));
导致内存泄漏。已分配内存,但从未解除分配。