保护多个shared_ptr到同一对象的最佳方法

Jor*_*uez 10 c++ shared-ptr c++11

将相同的指针发送到两个不同shared_ptr的指针是不好的,它会导致双重释放,如下所示:

int* p = new int;
std::shared_ptr<int> p1(p);
std::shared_ptr<int> p2(p); // BAD
Run Code Online (Sandbox Code Playgroud)

您可以通过以下方式实现相同目的std::enable_shared_from_this:

class Good: public std::enable_shared_from_this<Good>
{
public:
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};

int main()
{
    std::shared_ptr<Good> gp1(new Good);
    std::shared_ptr<Good> gp2 = gp1->getptr();
}
Run Code Online (Sandbox Code Playgroud)

但这仍然无法防范:

class Good: public std::enable_shared_from_this<Good>
{
public:
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};

int main()
{
    Good* p = new Good;
    std::shared_ptr<Good> gp3(p);
    std::shared_ptr<Good> gp4(p); // BAD
}
Run Code Online (Sandbox Code Playgroud)

如果您有这样的代码,这可能会成为一个问题:

void Function(std::shared_ptr<Good> p)
{
    std::cout << p.use_count() << '\n';
}

int main()
{
    Good* p = new Good;
    std::shared_ptr<Good> p1(p);
    Function(p);    // BAD
}
Run Code Online (Sandbox Code Playgroud)

为什么我会在有智能指针时使用常规指针?因为在性能关键代码(或为了方便)中,shared_ptr或weak_ptr的开销是不合需要的.

为了防止这个错误,我做了:

class CResource : public shared_ptr<Good>
{
public:
    CResource()
    {
    }

    CResource(std::shared_ptr<CBaseControl> c)
        : CResource(c)
    {
    }

private:
    CResource(CBaseControl* p)
    {
    }
};

void Function(CResource p)
{
    std::cout << p.use_count() << '\n';
}

int main()
{
    Good* p = new Good;
    CResource p1(std::shared_ptr<Good>(p));
    Function(p);    // Error
}
Run Code Online (Sandbox Code Playgroud)

如果有人试图Function用指针而不是a 来调用,这将导致编译器出错shared_ptr.它并不妨碍某人声明void Function(std::shared_ptr p),但我认为这不太可能.

这还不错吗?有没有更好的方法呢?

Kon*_*lph 27

解决方案很简单:首先没有原始指针拥有内存.这种模式:

int* p = new int;
std::shared_ptr<int> p1(p);
std::shared_ptr<int> p2(p); // BAD
Run Code Online (Sandbox Code Playgroud)

根本就不应该存在.从你的代码库中消除它.new在C++ 11中唯一合法的地方是作为构造函数调用智能指针(或非常低级别的东西)的参数.

也就是这样的代码:

std::shared_ptr<int> p1(new int);
Run Code Online (Sandbox Code Playgroud)

或者更好(不再new涉及裸体):

auto p1 = std::make_shared<int>();
Run Code Online (Sandbox Code Playgroud)

请注意,在代码中使用原始指针是很好的(但我在大多数C++代码中都会问到这一点).但是如果你使用原始指针,不要让它们拥有内存.要么指向自动存储(不需要资源管理),要么unique_ptr通过其get成员函数使用和访问原始指针.

  • 好点子.明确所有权语义足以使您的问题不再出现.另外,请记住,当不需要共享所有权时,您可以使用`unique_ptr`而无需开销. (3认同)
  • @Vino本身,这不会造成问题.你可以使用一个使用原始指针的函数.只是不要在任何地方删除它,或使用它来构造另一个智能指针. (2认同)
  • @Vino,使用_point_指向对象的原始指针很好.使用_own_对象的原始指针不太好,使用`shared_ptr`或`unique_ptr`或类似的类型来_own_一个对象.如果需要将所有权转移到另一个函数,请将智能指针传递给该函数. (2认同)
  • @Vino:你可以通过总是传递一个智能指针来防止错误的方法;-)替代方法在你的文档中明确表示代码的使用者不要做自己的内存管理.一旦你将其他可用的工具分类,那就和C++一样好. (2认同)

Jon*_*ely 10

这个例子甚至没有编译:

void Function(std::shared_ptr<Good> p)
{
    std::cout << p.use_count() << '\n';
}

int main()
{
    Good* p = new Good;
    std::shared_ptr<Good> p1(p);
    Function(p);    // BAD
}
Run Code Online (Sandbox Code Playgroud)

shared_ptr有一个explicit构造函数正是为了阻止这种情况发生.

要进行编译,您需要编写:

    Function( std::shared_ptr<Good>(p) );
Run Code Online (Sandbox Code Playgroud)

这显然是错误的,如果有人会犯这个错误他们就像这样做:

    Function( CResource(std::shared_ptr<Good>(p)) );
Run Code Online (Sandbox Code Playgroud)

那你为什么要打扰CResource呢?它添加了什么?

扩展Konrad Rudolph的优秀答案:

关于如何避免问题的问题的答案是遵循RAII习语,但要完全遵循它.

我们将忽略甚至不编译的示例并查看上面的示例:

Good* p = new Good;
std::shared_ptr<Good> gp3(p);
std::shared_ptr<Good> gp4(p); // BAD
Run Code Online (Sandbox Code Playgroud)

该代码无法遵循RAII惯用法.您获得了一个资源:

    Good* p = new Good;
Run Code Online (Sandbox Code Playgroud)

但是不要初始化RAII类型..

然后使用一些现有资源初始化对象:

    std::shared_ptr<Good> gp3(p);
Run Code Online (Sandbox Code Playgroud)

这也很糟糕.您应该在获取资源的同时初始化RAII类型,而不是单独初始化(甚至不能仅用一行分隔.)

然后你重复同样的错误:

    std::shared_ptr<Good> gp4(p); // BAD
Run Code Online (Sandbox Code Playgroud)

您已将此行"BAD",但实际上前面的两行只是糟糕.第三行将导致未定义的行为,但前两行允许该错误进入,当它应该变得更加困难时.如果你从来没有Good*闲逛那么你就不能用它进行初始化gp4,你需要说明shared_ptr<Good> gp4(gp3.get())哪个是明显错误的,没有人会这样做.

规则很简单:不要使用原始指针并将其放入shared_ptr.您未分配的原始指针不是资源获取,因此请勿将其用于初始化.同样适用于内部Function,它不应该采用原始指针并使用它来初始化一个shared_ptr取得该类型所有权的.

这是C++,因此不可能防止所有错误的编写代码的方法,你不能阻止有充分动机的白痴自己在脚下射击,但所有的指导方针和工具都可以防止它,如果你遵循准则.

当你有一个强制你通过传递原始指针来转移所有权的接口时,尝试替换接口以便它使用unique_ptr所有权转移,或者如果完全不可能那样做,那么尝试将接口包装在一个更安全的版本中使用unique_ptrfor所有权转移,并且只作为最后的手段,使用危险的界面,但非常明确地记录所有权的转移.