我应该在类构造函数内部还是外部初始化shared_ptr



给定以下示例代码:

#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(
  1. 512),这将导致调用运算符 new,返回指向堆上 int 的指针(内存分配)。
  2. 取消引用指针,以便为 Bar 的构造函数提供常量引用
  3. 然后,Bar 用 make_shared 返回的一个构造其shared_ptr(这部分是有效的)。此shared_ptr的 int 使用通过引用传递的 int 副本进行初始化。
  4. 然后函数返回,但由于没有变量记录从 new 返回的指针,因此没有什么可以释放内存。每个新内容都必须通过删除进行镜像,以便销毁对象并释放其内存。
  5. 因此内存泄漏

这取决于你想要实现什么。

如果你在内部需要一个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&

最新更新