给出以下示例代码:
#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的创建委托给类构造函数之间是否存在任何差异?
我认为第二种方式更好,因为我不必移动指针。但是,我经常看到第一种方式是在我阅读的代码中使用。
我应该使用哪一个?为什么?
答案 0 :(得分:6)
Foo是对的。
酒吧是令人厌恶的。它涉及内存泄漏,不安全的异常行为和不必要的副本。编辑:内存泄漏的解释。
解构这一行:
Bar bar(*new int(512));
导致这些操作:
答案 1 :(得分:3)
这取决于你想要达到的目标。
如果你需要内部shared_ptr
,因为你想要与之后创建的其他对象共享对象,第二种方式可能更好(显然除了可怕的构造函数调用之外)。
如果你想要一个现有对象的共享所有权(这是更常见的情况,真的),你没有选择,你需要使用第一种方式。
如果这些都不适用,您可能首先不需要shared_ptr
。
答案 2 :(得分:1)
第二种方式不正确;它泄漏了记忆。与
Bar::Bar(int &p) : ptr(std::make_shared<int>(p)) {
}
....
Bar bar(*new int(512));
由shared_ptr
生成的std::make_shared
获取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
提供相同的,所以有先例。这取决于你的课程是否这是一个好主意。
答案 3 :(得分:0)
两者都可以正常工作,但在main
中使用它们的方式并不一致。
当我看到构造函数接受引用(如Bar(int& p)
)时,我希望Bar持有引用。当我看到Bar(const int& p)
时,我希望它能够保留副本。
当我看到一个rvalue ref(不是通用的,比如Bar(int&& p)
我希望p在通过之后不会“幸存它的内容”。(好吧......对于那个没有意义的......)。
在任何情况下p
都包含int
,而不是pointer
,make_shared期望的是转发到int构造函数的参数(即...是一个int,而不是INT *)。
你的主要必须是
Foo foo(std::make_shared<int>(int(256)));
Bar bar(512);
这将使bar保持值512
的动态分配的可共享副本。
如果你执行Bar bar(*new int(512))
,你就可以保留一个“new int”的副本,其指针会被丢弃,因此int本身就会泄露。
一般来说,像*new something
这样的表达应该听起来像“不不不不不......”
但是你Bar
构造函数有一个问题:为了能够获取常量或表达式返回值,它必须采用const int&
,而不是int&
答案 4 :(得分:0)
如果您打算单独拥有堆分配的对象,我建议您接受std::unique_ptr
。它清楚地记录了意图,如果您需要,可以在内部std::shared_ptr
创建std::unique_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
,这很容易出错并且无法描述你的意图(如果我理解你的意图的话)。
答案 5 :(得分:0)
假设您仅使用int
作为示例,并且确实存在真实资源,那么它取决于您想要实现的目标。
第一种是典型的依赖注入,其中对象是通过构造函数注入的。它的优点在于它可以简化单元测试。
第二种情况只是在构造函数中创建对象,并使用通过构造函数传递的值对其进行初始化。
顺便说一下,注意你如何初始化。这个:
Bar bar(*new int(512));
导致内存泄漏。已分配内存,但从未取消分配。