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

时间:2015-01-13 16:30:13

标签: c++ c++11 smart-pointers

给出以下示例代码:

#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的创建委托给类构造函数之间是否存在任何差异?

我认为第二种方式更好,因为我不必移动指针。但是,我经常看到第一种方式是在我阅读的代码中使用。

我应该使用哪一个?为什么?

6 个答案:

答案 0 :(得分:6)

Foo是对的。

酒吧是令人厌恶的。它涉及内存泄漏,不安全的异常行为和不必要的副本。

编辑:内存泄漏的解释。

解构这一行:

Bar bar(*new int(512));

导致这些操作:

  1. 调用new int(512),这会调用operator new,返回指向堆上int的指针(内存分配)。
  2. 取消引用指针以便为Bar
  3. 的构造函数提供const引用
  4. Bar然后用make_shared返回的shared_ptr构造它(这部分是有效的)。这个shared_ptr的int初始化为一个通过引用传递的int的副本。
  5. 然后函数返回,但由于没有变量记录了从new返回的指针,因此没有任何东西可以释放内存。必须通过删除镜像每个新的,以便销毁对象并释放其内存。
  6. 因此内存泄漏

答案 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获取p512)的值来构建一个新的共享指针,该指针负责新作品记忆;它不对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));

导致内存泄漏。已分配内存,但从未取消分配。