C++ 我应该在类构造函数内部还是外部初始化共享\u ptr?

C++ 我应该在类构造函数内部还是外部初始化共享\u ptr?,c++,c++11,smart-pointers,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); pr

给出以下示例代码:

#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(std::shared_ptr p);
私人:
std::共享的ptr;
};
Foo::Foo(std::shared_ptr p):ptr(std::move(p)){
}
分类栏{
公众:
酒吧(国际及太平洋);;
私人:
std::共享的ptr;
};
酒吧:酒吧(内部和外部):ptr(标准:共享){
}
int main(){
Foo-Foo(std::make_shared(int(256));
条形图(*新整数(512));
返回0;
}
Foo和Bar都能正常工作。但是,在调用构造函数时创建共享的_ptr,然后使用std::move转移所有权,然后只传递对对象的引用并将共享的_ptr的创建委托给类构造函数,这两者之间有什么区别吗

我认为第二种方法更好,因为我不必移动指针。然而,我在阅读的代码中基本上看到了第一种方法

我应该使用哪一个?为什么?Foo是正确的

酒吧是令人憎恶的。它涉及内存泄漏、不安全的异常行为和不必要的拷贝

编辑:内存泄漏的解释

解构这条线:

Bar bar(*new int(512));
这些操作的结果:

  • 调用newint(512),这将导致调用运算符new,返回指向堆上int的指针(内存分配)
  • 取消对指针的引用,以便为Bar的构造函数提供常量引用
  • 然后,Bar用make_shared返回的一个构建其共享的_ptr(这部分很有效)。此共享ptr的int使用通过引用传递的int的副本进行初始化
  • 然后函数返回,但由于没有变量记录从new返回的指针,因此无法释放内存。为了销毁对象并释放其内存,每个新对象都必须通过delete进行镜像
  • 因此内存泄漏

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

    如果您在内部需要一个
    shared\u ptr
    ,因为您想与稍后创建的其他对象共享该对象,那么第二种方法可能更好(除了那可怕的构造函数调用)

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


    如果这两种方法都不适用,那么首先您可能不需要共享的ptr。

    第二种方法是不正确的;它泄漏内存。与

    Bar::Bar(int &p) : ptr(std::make_shared<int>(p)) {
    }
    
    ....
    
    Bar bar(*new int(512));
    
    …但看看这个这纯粹是邪恶的。没有人期望构造函数的引用参数要求它引用新对象将负责的堆对象

    你可以写得更理智些

    Bar::Bar(int *p) : ptr(p) {
    }
    
    ....
    
    Bar bar(new int(512));
    

    事实上,如果您愿意,您可以给
    Foo
    第二个构造函数来实现这一点。函数签名使指针必须指向堆分配的对象变得多么清晰,这是一个有争议的问题,但是
    std::shared_ptr
    提供了同样的功能,因此有先例。这取决于您的类所做的工作,这是否是一个好主意。

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

    当我看到一个构造函数获取一个引用(比如
    Bar(int&p)
    )时,我希望Bar保存一个引用。当我看到
    Bar(const int&p)
    时,我希望它能保存一份副本。 当我看到一个右值ref(不是通用的,比如
    Bar(int&&p)
    时,我希望p在传递后不会“保留其内容”(好吧……对于it来说,这并没有什么意义……)

    在任何情况下,
    p
    持有一个
    int
    ,而不是一个
    指针,make_shared期望的是转发给int构造函数的参数(即…一个int,而不是int*)

    你的主要目标是什么

    Foo foo(std::make_shared<int>(int(256)));
    Bar bar(512);
    
    Foo-Foo(std::make_shared(int(256));
    棒材(512);
    
    这将使bar保存动态分配的值
    512
    的可共享副本

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

    一般来说,像
    *newsomething
    这样的表达应该听起来像“no no no no no…”


    但是您的
    Bar
    构造函数有一个问题:为了能够接受常量或表达式返回值,它必须接受
    const int&
    ,而不是
    int&
    如果您的意图是单独拥有堆分配的对象,我建议您接受
    std::unique\u 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));
    }
    
    #包括
    福班{
    公众:
    Foo(std::unique_ptr p);
    私人:
    std::共享的ptr;
    };
    Foo::Foo(std::unique_ptr p):ptr(std::move(p)){
    }
    int main(){
    Foo-Foo(std::make_unique(512));
    }
    

    不要使用
    Bar
    ,它很容易出错,并且无法描述您的意图(如果我正确理解您的意图)。

    假设您仅使用
    int
    作为示例,并且存在真正的资源,那么这取决于您想要实现什么

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

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


    顺便说一下,请注意如何初始化。这是:

    Bar bar(*new int(512));
    

    导致内存泄漏。内存已分配,但从未解除分配。

    我更喜欢使用
    std::make_shared
    直接初始化。如果
    new
    抛出错误,则表明存在明确的内存泄漏。代码泄漏内存,这两种情况完全不同。您可以更好地论证“内存泄漏”和“exeption unsafety”这很好,但问题不在于酒吧本身,而在于它的使用方式。但是你说的是酒吧,而真正的p
    Bar bar(*new int(512));