我是否应该从传递给一个函数的 `new` 中删除指向 `shared_ptr` 的指针?

Should I delete pointer from `new` passed to a function which makes into a `shared_ptr`?

在下面的代码示例中:

#include <iostream>

class Foo{
};

class Bar{
public:
    void addFoo(Foo *foo){
        auto my_foo = std::shared_ptr<Foo>(foo);
    }
};

int main() {
    auto bar = Bar();
    bar.addFoo(new Foo());
    return 0;
}

我是否需要通过 bar.addFoo(new Foo) 调用清理在 main() 中创建的指针,或者这将由创建 shared_ptrBar 处理它的?我的理解是 auto my_foo = std::shared_ptr<Foo>(foo); 将使用复制构造函数将此指针复制到 my_foo 中,而使原始指针悬空,对吗?

执行此操作的正确 C++ 方法如下:

#include <iostream>

class Foo{

};

class Bar{
public:
    void addFoo(Foo foo){
        auto my_foo = std::make_shared<Foo>(foo);
    }
    
};

int main() {
    auto bar = Bar();
    bar.addFoo(Foo());
    return 0;
}

这避免了任何原始指针或裸新,并且完全是异常安全的。此外,std::make_shared 引入了一些性能优势。 这里的一件令人困惑的事情是代码似乎不必要地复制了 Foo 对象,但是,自 C++17 以来,由于 Return 值优化 (RVO),您保证没有副本完全没有(将 Foo 作为参数传递给 addFoo 时)。

原始指针不管理生命周期结束,但共享指针可以。当您从原始指针创建共享指针时,共享指针取得对象的所有权。这意味着当指向它的最后一个共享指针超出范围时,该对象将被销毁。

在您的代码中,my_foo 获得使用 new Foo() 创建的对象的所有权,在 addFoo returns 时超出范围,并且因为它包含唯一的共享引用,正确销毁对象。

采用原始指针的构造函数的真正想法是将所有权传递给 std::shared_ptr。所以,不,您不必 delete 将原始指针传递给 std::shared_ptr。这样做会导致双删,也就是UB

请注意,通常传递原始指针是危险的。考虑以下更通用的示例:

void addFoo(Foo *foo){
        // some code which could throw an exception
        auto my_foo = std::shared_ptr<Foo>(foo);
    }

如果在构造my_foo之前抛出异常,foo就会泄漏。

如果您没有特殊原因要传递原始指针,请考虑以下备选方案:

class Bar {
public:
    template<class... Args>
    void addFoo(Args... args){
        auto my_foo = std::make_shared<Foo>(args...);
    }  
};

int main() {
    auto bar = Bar();
    bar.addFoo();
    return 0;
}

此处传递参数(如果有)以在 addFoo() 中构造 Foo,而不是在调用 addFoo().

之前构造 Foo

完美转发args...如果需要可以使用:

    template<class... Args>
    void addFoo(Args&&... args){
        auto my_foo = std::make_shared<Foo>(std::forward<Args>(args)...);
    }

您可以使用 make_shared 创建共享指针。如果你想在 main 中构造 Foo (例如,因为你在那里有可用的参数),那么在构造点使用 make_shared 并传递 shared_ptr on.

#include <iostream>

class Foo{
    ~Foo() { std::cout << "Foo destructed" << std::endl; }
};

class Bar{
public:
    void addFoo(std::shared_ptr<Foo> foo){
        auto my_foo = foo;
    }        
};

int main() {
    auto bar = Bar();
    bar.addFoo(std::make_shared<Foo>());
    return 0;
}

delete 也会调用你的析构函数。您可以通过打印消息来测试共享指针是否破坏了您的对象,或者是否需要删除。

你写的代码是正确的。但是在现代 C++ 中,您不应该使用原始指针 newdelete 除非您必须与这样做的代码进行互操作。如果你能帮助它(如果问题评论有任何迹象,你可以),请一直使用智能指针:

#include <iostream>
#include <memory>

class Foo {};

class Bar {
public:
    void addFoo(std::unique_ptr<Foo> foo) {
        auto my_foo = std::shared_ptr<Foo>(std::move(foo));
    }
};

int main() {
    auto bar = Bar();
    bar.addFoo(std::make_unique<Foo>());
    return 0;
}

上面,addFoo 成员函数接收指针作为 unique_ptr,并使用 std::move 将指针的所有权从 unique_ptr 转移到 shared_ptr 不复制所指对象;在构造 shared_ptr 之后,unique_ptr 处于空状态。您也可以让 addFoo 直接接收 shared_ptr,或在成员函数内就地构造对象,如 Evg 的回答。

使用 unique_ptr 而不是原始指针清楚地表明该方法打算取得分配的所有权,并鼓励调用者自己使用智能指针,从而降低他们忘记 delete他们稍后分配。