给定以下示例代码:
#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的创建委托给类构造函数之间有什么区别吗?
我认为第二种方法更好,因为我不必移动指针。但是,我主要看到我正在阅读的代码中使用第一种方法。
我应该使用哪一个,为什么?
Foo是正确的。
酒吧是可憎的。它涉及内存泄漏、不安全的异常行为和不必要的副本。
编辑:内存泄漏的解释。
解构这条线:
Bar bar(*new int(512));
导致以下操作:
- 调用 new int(
- 512),这将导致调用运算符 new,返回指向堆上 int 的指针(内存分配)。
- 取消引用指针,以便为 Bar 的构造函数提供常量引用
- 然后,Bar 用 make_shared 返回的一个构造其shared_ptr(这部分是有效的)。此shared_ptr的 int 使用通过引用传递的 int 副本进行初始化。
- 然后函数返回,但由于没有变量记录从 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
提供了相同的内容,因此有先例。这取决于你的类做什么,这是否是一个好主意。
假设你只是用int
作为例子,并且有一个真正的资源,那么这取决于你想要实现什么。
第一种是典型的依赖注入,其中对象通过构造函数注入。好的一面是它可以简化单元测试。
第二种情况只是在构造函数中创建对象,并使用通过构造函数传递的值对其进行初始化。
顺便说一下,请注意如何初始化。这:
Bar bar(*new int(512));
导致内存泄漏。内存已分配,但从不解除分配。
如果你的意图是单独拥有堆分配的对象,我建议你接受一个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
,它很容易出错并且无法描述您的意图(如果我正确理解您的意图)。
两者都可以正常工作,但在main
中使用它们的方式并不一致。
当我看到一个构造函数接受引用(如Bar(int& p)
)时,我希望 Bar 持有一个引用。当我看到Bar(const int& p)
时,我希望它能保存一份副本。当我看到一个右值引用(不是通用的,就像Bar(int&& p)
我希望 p 在通过后不会"幸存其内容"。(嗯...对于一个没有那么有意义的它...
在任何情况下,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来保存你的"新int"的副本,它的指针被丢弃,因此int本身泄漏。
一般来说,像*new something
这样的表达应该听起来像"不不不不......"
但是你Bar
构造函数有一个问题:为了能够接受常量或表达式返回值,它必须采用const int&
,而不是int&