草庐IT

std::weak_ptr<void>绑定到std::shared_ptr<T>

lycpp 2023-04-19 原文

最近在忙自己的研究生科研工作和尽量在不看源码的情况下写一个玩具版的muduo(我已经看过陈硕的《Linux多线程服务端编程:使用muduo C++网络库》,相当于按自己的理解再写一遍),没太有时间写C++对象模型的后面部分,等组会开完后再继续写。
今天就写一下几天前看到的一个小技巧,也即标题:std::weak_ptr<void>绑定到所有类型的std::shared_ptr

std::weak_ptr

我们知道weak_ptr目的是为了防止只使用std::shared_ptr导致的循环引用,从而导致内存泄漏。一个经典的例子如下:

#include <iostream>
#include <vector>
#include <memory>
#include <string>

class Child;

class Parent {
public:
    Parent(const std::string& name)
        : m_name(name),
          m_children()
    {}
    ~Parent();

    void addChild(std::shared_ptr<Child>& child) {
        m_children.push_back(child);
    }

    const std::string&
    getName() const {
        return m_name;
    }

    std::vector<std::shared_ptr<Child>>&
    getChildren() {
        return m_children;
    }
private:
    std::string m_name;
    std::vector<std::shared_ptr<Child>> m_children; // Parent对象使用shared_ptr来持有Child对象
};

class Child {
public:
    Child(const std::string& name, std::shared_ptr<Parent>& parent)
        : m_name(name),
          m_parent(parent)
    {}

    ~Child() {
        std::cout << m_name << "'s destruction" << std::endl;
    }

    void showParentName() const {
        std::shared_ptr<Parent> parent = m_parent.lock();
        if (parent) {
            std::cout << m_name << "'s parent: " << parent->getName() << std::endl;
        } else {
            std::cout << m_name << "'s parent has destructed" << std::endl;
        }
    }

private:
    std::string m_name;
    std::weak_ptr<Parent> m_parent; // Child对象使用weak_ptr来引用Parent对象
};

Parent::~Parent() {
    std::cout << m_name << "'s destruction" << std::endl;
}

void func() {
    std::shared_ptr<Parent> parent = std::make_shared<Parent>("Parent01");
    std::shared_ptr<Child> child = std::make_shared<Child>("Child01", parent);
    parent->addChild(child);
    child->showParentName();
}

int main() {
    func();
}

// Output:
//  Child01's parent: Parent01
//  Parent01's destruction
//  Child01's destruction

我们可以看到ParentChild对象均正常析构了。

std::weak_ptr与其绑定的std::shared_ptr

在上面的代码中,如果有其他地方持有std::shared_ptr<Child>,那么在Parent析构时,被该std::share_ptr<Child>持有的Child对象不会析构,而且Child::showParentName会正常识别出其Parent对象已经被析构。这就是std::weak_ptr能判断其绑定的std::shared_ptr管理的对象是否已经析构。
但有一个问题,如果我只是用std::weak_ptr来判断其绑定的std::shared_ptr管理的对象是否已经析构,但其绑定的std::shared_ptr管理的对象类型不一定怎么办?正如标题所言,std::weak_ptr<void>可以绑定到所有类型的std::shared_ptr,所以只要使用一个std::weak_ptr即可。
我知道这个用法的来源是陈硕的muduo网络库,在muduo中,Channel用于管理一个socket描述符的读、写、出错事件,并调用相应的回调,但有一个问题是Channel类并不持有该socket描述符(只存有该socket描述符,但其生命期并不归Channel管理),那如何判断Channel对应的管理socket描述符的类是否已经析构呢(因为Channel的读写出错回调往往是通过std::bind或者lambda包裹的socket描述符的持有者的private方法,如果持有者已经析构,再调用回调会导致段错误从而core dump)?muduo就是再Channel中使用std::weak_ptr<void>。其有一个方法Channel::tie,接受const std::shared_ptr<void>&类型的参数,此参数要求传入持有socket描述符管理者对象的std::shared_ptrmuduo将此参数赋值给给std::weak_ptr<void>对象,使其可以监控socket描述符管理者对象是否已经析构。部分代码如下:

// muduo/net/Channel.cc

void Channel::tie(const std::shared_ptr<void>& obj)
{
  tie_ = obj;   // std::weak_ptr<void> tie_
  tied_ = true; // bool tied_
}

void Channel::handleEvent(Timestamp receiveTime)
{
  std::shared_ptr<void> guard;
  if (tied_)
  {
    guard = tie_.lock();
    if (guard)
    {
      handleEventWithGuard(receiveTime);
    }
  }
  else
  {
    handleEventWithGuard(receiveTime);
  }
}

这样的用法是合法的吗?我们可以在cppreference上查看一下std::shared_ptr和std::weak_ptr的相关信息。
可以看到std::shared_ptr有如下的构造函数:

// https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
template<typename T>    // 这两行是我自己加的,
class std::shared_ptr { // 说明里面是该类的成员函数
// ...  (1) - (2)
template< class Y >
explicit shared_ptr( Y* ptr ); // (3)

// ... (4)-(13)
};

可以看到可以由std::shared_ptr<Y>构造std::shared_ptr<T>,要求是:

For (3-4,6), Y* must be convertible to T*. // until C++17

也就是只要T*能转化为Y*即可,而所有类型的指针都可以转化为void*,所以std::shared_ptr<T>构造std::shared_ptr<void>是可以的,而且他们管理着相同的对象。测试如下:

#include <iostream>
#include <memory>

class Test {
public:
    ~Test() {
        std::cout << "Test::~Test()" << std::endl;
    }
};

int main() {
    std::shared_ptr<void> pvoid;
    {
        std::shared_ptr<Test> pTest = std::make_shared<Test>();
        pvoid = pTest;
    }
    std::cout << "pTest has destructed" << std::endl;
}

// Output:
//  pTest has destructed
//  Test::~Test()

然后std::shared_ptr<void>构造std::weak_ptr<void>就是理所当然的了。
那能不能由std::shared_ptr<T>直接构造std::weak_ptr<void>呢?按理来说是可以的,我们在cppreference里面找一下可以发现:

// https://en.cppreference.com/w/cpp/memory/weak_ptr/weak_ptr
template<T>      // 这两行是我自己加的,
std::weak_ptr {  // 说明里面是该类的成员函数
    template< class Y >
    weak_ptr( const std::shared_ptr<Y>& r ) noexcept;
};

The templated overloads don't participate in the overload resolution unless Y* is implicitly convertible to T*

即如果Y*能隐式转化为T*的话是可以的,而我们知道一般的指针类型(除了成员指针和成员函数指针)都可以隐式转化为void*类型,所以由std::share_ptr<T>构造std::weak_ptr<void>是可行的。验证如下:

#include <iostream>
#include <memory>

class Test {
public:
    ~Test() {
        std::cout << "Test::~Test()" << std::endl;
    }
};

std::weak_ptr<void> func() {
    std::shared_ptr<Test> pTest = std::make_shared<Test>();
    std::weak_ptr<void> pVoidWeak = pTest;
    std::shared_ptr<void> pVoid = pVoidWeak.lock();
    if (pVoid) {
        std::cout << "Test object exists" << std::endl;
    } else {
        std::cout << "Test object has been destructed" << std::endl;
    }
    return pVoidWeak;
}

int main() {
    auto pVoidWeak = func();
    std::shared_ptr<void> pVoid = pVoidWeak.lock();
    if (pVoid) {
        std::cout << "Test object exists" << std::endl;
    } else {
        std::cout << "Test object has been destructed" << std::endl;
    }
}

// Output:
//  Test object exists
//  Test::~Test()
//  Test object has been destructed

有关std::weak_ptr<void>绑定到std::shared_ptr<T>的更多相关文章

  1. ruby-on-rails - rails : keeping DRY with ActiveRecord models that share similar complex attributes - 2

    这似乎应该有一个直截了当的答案,但在Google上花了很多时间,所以我找不到它。这可能是缺少正确关键字的情况。在我的RoR应用程序中,我有几个模型共享一种特定类型的字符串属性,该属性具有特殊验证和其他功能。我能想到的最接近的类似示例是表示URL的字符串。这会导致模型中出现大量重复(甚至单元测试中会出现更多重复),但我不确定如何让它更DRY。我能想到几个可能的方向...按照“validates_url_format_of”插件,但这只会让验证干给这个特殊的字符串它自己的模型,但这看起来很像重溶液为这个特殊的字符串创建一个ruby​​类,但是我如何得到ActiveRecord关联这个类模型

  2. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  3. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

  4. ruby-on-rails - Nokogiri:使用 XPath 搜索 <div> - 2

    我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll

  5. ruby-on-rails - 如何重构 "shared"方法? - 2

    我正在使用RubyonRails3.2.2,我想从我的模型/类中“提取”一些方法。也就是说,在不止一个类/模型中,我有一些方法(注意:方法与用户授权相关,并被命名为“CRUD方式”),这些方法实际上是相同的;所以我认为DRY方法是将这些方法放在“共享”模块或类似的东西中。实现该目标的常见且正确的方法是什么?例如,我应该将“共享”代码放在哪里(在哪些目录和文件中)?如何在我的类/模型中包含提到的方法?你有什么建议?注意:我正在寻找“RubyonRails制作东西的方式”。 最佳答案 一种流行的方法是使用ActiveSupport关注点

  6. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

  7. ruby-on-rails - 没有参数的 `<<`(小于两倍)是什么意思? - 2

    我在一个我想在formtasticGem中覆盖的方法中找到了这个。该方法如下所示:defto_htmlinput_wrappingdohidden_field_html是什么意思?在第三行做什么?我知道它对数组有什么作用,但在这里我不知道。 最佳答案 你可以这样读:hidden_field_htmllabel_with_nested_checkbox是连接到hidden_​​field_html末尾的参数-为了“清晰”,他们将其分成两行 关于ruby-on-rails-没有参数的`

  8. ruby-on-rails - 找不到 gem railties (>= 0.a) (Gem::GemNotFoundException) - 2

    我已经看到了一些其他的问题,尝试了他们的建议,但没有一个对我有用。我已经使用Rails大约一年了,刚刚开始一个新的Rails项目,突然遇到了问题。我卸载并尝试重新安装所有Ruby和Rails。Ruby很好,但Rails不行。当我输入railss时,我得到了can'tfindgemrailties。我当前的Ruby版本是ruby2.2.2p95(2015-04-13修订版50295)[x86_64-darwin15],尽管我一直在尝试通过rbenv设置ruby​​2.3.0。如果我尝试rails-v查看我正在运行的版本,我会得到同样的错误。我使用的是MacOSXElCapitan版本10

  9. ruby-on-rails - 连接字符串时如何在 <%=%> block 内输出 html_safe? - 2

    考虑一下:现在这些情况:#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2我需要用其他字符串输出URL。我如何保证&符号不会被转义?由于我无法控制的原因,我无法发送&。求助!把我的头发拉到这里:\编辑:为了澄清,我实际上有一个像这样的数组:@images=[{:id=>"fooid",:url=>"http://

  10. Ruby -> 写入二维数组 - 2

    我正在处理http://prepwork.appacademy.io/mini-curriculum/array/中概述的数组问题我正在尝试创建函数my_transpose,它接受一个矩阵并返回其转置。我对写入二维数组感到很困惑!这是一个代码片段,突出了我的困惑。rows=[[0,1,2],[3,4,5],[6,7,8]]columns=Array.new(3,Array.new(3))putscolumns.to_s#Outputisa3x3arrayfilledwithnilcolumns[0][0]=0putscolumns.to_s#Outputis[[0,nil,nil],[

随机推荐