2015-01-13 51 views
2

考虑下面的示例代码:我是否应该在类构造函数内部或外部初始化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的创建委托给类构造函数,是否有任何区别?

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

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

+1

我更喜欢用'std :: make_shared'进行直接初始化。如果'new'抛出一个确定的内存泄漏。 –

+2

代码泄漏内存,这两件事情完全不同 –

回答

6

Foo是正确的。

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

编辑:内存泄漏的解释。

解构这一行:

Bar bar(*new int(512)); 

结果在这些操作:

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

你能更好地论证“内存泄漏”和“exeption unsafety”吗? –

+0

我会编辑答案 –

+0

这很好,但问题不在Bar本身,而是它被使用的方式。但是,你声明这个错误是Bar,而真正的问题是* new(这使得指针返回新的不可用,因此不可删除) –

3

这取决于你想要达到的目标。

如果你在内部需要一个shared_ptr,因为你想与你创建的其他对象共享对象,第二种方式可能会更好(除非那个可怕的构造函数明显)。

如果您希望共享现有对象的所有权(这是更常见的情况),您没有选择权,而需要使用第一种方法。

如果这些都不适用,那么您可能首先不需要shared_ptr

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提供了相同的内容,所以有先例。这取决于你的班级做什么,这是否是一个好主意。

+0

为什么让用户在第一时间做堆分配?您可以通过非拥有(右值)引用轻松地获取参数,并将其复制(移动)到“Bar”情况下的'shared_ptr'中。 – ComicSansMS

+0

你可能想要拥有它的一个原因是你需要从std :: unique_ptr的所有权中获得所有权,或者处理使用'std :: auto_ptr'或'boost :: scoped_ptr'的遗留代码。有时候你不能自由选择你的图书馆,然后像这样的东西可以减少样板代码。这可能不是你经常使用的工具,但它可以合理安排。有时。 – Wintermute

+0

好点。然而,在那种情况下,我认为'Foo'方法更可取,这使得所有权被传递给对象的构造函数变得更加明显。尽管如此,关于为什么“Bar”的原始代码被破坏的很好的分析。 – ComicSansMS

0

两者都可以正常工作,但在main中使用它们的方式并不一致。

当我看到一个构造函数接受一个引用(如Bar(int& p)),我期望Bar拥有一个引用。当我看到Bar(const int& p)我希望它保留一份副本。 当我看到一个右值裁判(不具有普遍性,像Bar(int&& p)我期待p不是“幸存的内容”通过后。(嗯......对于它,这不是有意义的......)。

在任何情况下p持有int,而不是一个pointer,什么make_shared预计是参数转发到int构造函数(即...一个int,不是int *)。

你的主要所以必须

Foo foo(std::make_shared<int>(int(256))); 
Bar bar(512); 

这将使栏保存一个动态分配的值的可共享副本512

如果你这样做Bar bar(*new int(512))你会让你保存你的“new int”的副本,它的指针会被丢弃,因此int本身就会泄漏。

一般情况下,像*new something表达听起来应该是“没有没有没有没有没有......”

但你Bar构造有一个问题:要能采取常量或表达式返回值,则必须采取const int&,而不是int&

0

如果你的意图是采取一个堆分配对象的唯一所有权,我建议你接受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,很容易出错,未能描述你的意图(如果我理解正确你的意图)。

0

假设您仅仅使用int作为示例,并且有一个实际的资源,则取决于您想要实现的目标。

第一种是典型的依赖注入,其中通过构造函数注入对象。好的一面是它可以简化单元测试。

第二种情况只是在构造函数中创建对象,并使用通过构造函数传递的值来初始化它。


顺便说一句,小心你如何初始化。此:

Bar bar(*new int(512)); 

导致内存泄漏。内存已分配,但从未释放。

相关问题