【问题标题】:Unexpected invocation of the destructor after an invokation of a product of std::bind在调用 std::bind 的乘积后意外调用析构函数
【发布时间】:2020-04-14 07:38:32
【问题描述】:

有一个使用boost::asio::io_context的简单例子

https://github.com/unegare/boost-ex/blob/500e46f4d3b41e2abe48e2deccfab39d44ae94e0/main.cpp


    #include <boost/asio.hpp>
    #include <boost/beast/core.hpp>
    #include <boost/beast/http.hpp>
    #include <boost/beast/version.hpp>
    #include <thread>
    #include <vector>
    #include <memory>
    #include <mutex>
    #include <chrono>
    #include <iostream>
    #include <exception>

    std::mutex m_stdout;

    class MyWorker {
      std::shared_ptr<boost::asio::io_context> io_context;
      std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
    public:
      MyWorker(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard):
        io_context(_io_context), work_guard(_work_guard) {}
      MyWorker(const MyWorker &mw): io_context(mw.io_context), work_guard(mw.work_guard) {
        m_stdout.lock();
        std::cout << "[" << std::this_thread::get_id() << "] MyWorker copy constructor" << std::endl;
        m_stdout.unlock();
      }
      MyWorker(MyWorker &&mw): io_context(std::move(mw.io_context)), work_guard(std::move(mw.work_guard)) {
        m_stdout.lock();
        std::cout << "[" << std::this_thread::get_id() << "] MyWorker move constructor" << std::endl;
        m_stdout.unlock();
      }
      ~MyWorker() {}

      void operator() () {
        m_stdout.lock();
        std::cout << "[" << std::this_thread::get_id() << "] Thread Start" << std::endl;
        m_stdout.unlock();

        while(true) {
          try {
            boost::system::error_code ec;
            io_context->run(ec);
            if (ec) {
              m_stdout.lock();
              std::cout << "[" << std::this_thread::get_id() << "] MyWorker: received an error: " << ec << std::endl;
              m_stdout.unlock();
              continue;
            }
            break;
          } catch (std::exception &ex) {
            m_stdout.lock();
            std::cout << "[" << std::this_thread::get_id() << "] MyWorker: caught an exception: " << ex.what() << std::endl;
            m_stdout.unlock();
          }
        }

        m_stdout.lock();
        std::cout << "[" << std::this_thread::get_id() << "] Thread Finish" << std::endl;
        m_stdout.unlock();
      }
    };

    class Client: public std::enable_shared_from_this<Client> {
      std::shared_ptr<boost::asio::io_context> io_context;
      std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
      std::shared_ptr<boost::asio::ip::tcp::socket> sock;
      std::shared_ptr<std::array<char, 512>> buff;
    public:
      Client(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard, std::shared_ptr<boost::asio::ip::tcp::socket> &_sock):
        io_context(_io_context), work_guard(_work_guard), sock(_sock) {
        buff = std::make_shared<std::array<char,512>>();
        m_stdout.lock();
        std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " with args" << std::endl;
        m_stdout.unlock();
      }
      Client(const Client &cl): io_context(cl.io_context), work_guard(cl.work_guard), sock(cl.sock), buff(cl.buff) {
        m_stdout.lock();
        std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " copy" << std::endl;
        m_stdout.unlock();
      }
      Client(Client &&cl): io_context(std::move(cl.io_context)), work_guard(std::move(cl.work_guard)), sock(std::move(cl.sock)), buff(std::move(cl.buff)) {
        m_stdout.lock();
        std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " move" << std::endl;
        m_stdout.unlock();
      }
      ~Client() {
        m_stdout.lock();
        std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " buff.use_count: " << buff.use_count() << " | sock.use_count: " << sock.use_count() << " | io_context.use_count: " << io_context.use_count() << std::endl;
        m_stdout.unlock();
      }

      void OnConnect(const boost::system::error_code &ec) {
        std::cout << __FUNCTION__ << std::endl;
        if (ec) {
          m_stdout.lock();
          std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
          m_stdout.unlock();
        } else {
    //      buff = std::make_shared<std::array<char, 512>>();
          char req[] = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
          memcpy(buff->data(), req, strlen(req));
          m_stdout.lock();
          std::cout << req << std::endl;
          m_stdout.unlock();
          sock->async_write_some(boost::asio::buffer(buff->data(), strlen(buff->data())), std::bind(std::mem_fn(&Client::OnSend), this, std::placeholders::_1, std::placeholders::_2));
        }
        std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
      }

      void OnSend(const boost::system::error_code &ec, std::size_t bytes_transferred) {
        std::cout << __FUNCTION__ << " use_count: " << io_context.use_count() << std::endl;
        if (ec) {
          m_stdout.lock();
          std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
          m_stdout.unlock();
        } else {
        std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
          buff->fill(0);
        std::cout << __FUNCTION__ << std::endl;
          sock->async_read_some(boost::asio::buffer(buff->data(), buff->size()), std::bind(std::mem_fn(&Client::OnRecv), this, std::placeholders::_1, std::placeholders::_2));
        }
      }

      void OnRecv(const boost::system::error_code &ec, std::size_t bytes_transferred) {
        std::cout << __FUNCTION__ << std::endl;
        if (ec) {
          m_stdout.lock();
          std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
          m_stdout.unlock();
        } else {
          m_stdout.lock();
          std::cout << buff->data() << std::endl;
          m_stdout.unlock();
        }
      }
    };

    int main () {
      std::shared_ptr<boost::asio::io_context> io_context(std::make_shared<boost::asio::io_context>());
      std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard(
        std::make_shared<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> (boost::asio::make_work_guard(*io_context))
      );
      MyWorker mw(io_context, work_guard);
      std::vector<std::thread> vth;
      vth.reserve(1);
      for (int i = 1; i > 0; --i) {
        vth.emplace_back(mw);
      }
      std::shared_ptr<Client> cl = 0;
      try {
        boost::asio::ip::tcp::resolver resolver(*io_context);
        boost::asio::ip::tcp::resolver::query query("unegare.info", "80");
        boost::asio::ip::tcp::endpoint ep = *resolver.resolve(query);
        m_stdout.lock();
        std::cout << "ep: " << ep << std::endl;
        m_stdout.unlock();

        std::shared_ptr<boost::asio::ip::tcp::socket> sock(std::make_shared<boost::asio::ip::tcp::socket>(*io_context));
        std::shared_ptr<Client> cl2(std::make_shared<Client>(io_context, work_guard, sock));
        cl = cl2->shared_from_this();
        m_stdout.lock();
        std::cout << "HERE: use_count = " << cl.use_count() << std::endl;
        m_stdout.unlock();
        sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
        std::this_thread::sleep_for(std::chrono::duration<double>(1));
        m_stdout.lock();
        std::cout << "AFTER CALL" << std::endl;
        m_stdout.unlock();
    //    asm volatile ("");
      } catch (std::exception &ex) {
        m_stdout.lock();
        std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
        m_stdout.unlock();
      }
      try {
        char t;
        std::cin >> t;
        work_guard->reset();
    //    std::this_thread::sleep_for(std::chrono::duration<double>(1));
    //    std::cout << "Running" << std::endl;
    //    io_context->run();
      } catch (std::exception &ex) {
        m_stdout.lock();
        std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
        m_stdout.unlock();
      }
      std::for_each(vth.begin(), vth.end(), std::mem_fn(&std::thread::join));
      return 0;
    }

标准输出:

[140487203505984] MyWorker copy constructor
[140487203505984] MyWorker move constructor
[140487185372928] Thread Start
ep: 95.165.130.37:80
[140487203505984] Client with args
HERE: use_count = 2
[140487203505984] Client copy
[140487203505984] Client move
[140487203505984] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
[140487185372928] Client move
[140487185372928] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
OnConnect
GET / HTTP/1.1
Host: unegare.info


OnConnect use_count: 2
[140487185372928] ~Client buff.use_count: 2 | sock.use_count: 3 | io_context.use_count: 5
Segmentation Fault (core dumped)

但是对Client对象的引用错误导致的segfault理解有点问题。

但我不明白为什么cl2在调用后会被破坏

sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));

在 162 行。

还有……为什么要调用复制构造函数? 从上面的stdout 可以看出。

【问题讨论】:

  • 请发布一个完整的示例来重现该问题。根据您的描述,我猜测为调用bind() 创建的函数对象复制了相关对象。
  • @DietmarKühl 在主题第一行的链接中有一个完整的示例。只有一个文件。
  • 此链接可能会更改,从而使问题/答案变得毫无价值。因此,问题需要在这里完成。
  • 这不是一个真正的简单示例。

标签: c++ c++11 boost boost-asio shared-ptr


【解决方案1】:

您不会使用输出跟踪 cl2 的破坏:cl2std::shared_ptr&lt;Client&gt;。您似乎在跟踪 Client 对象的构造。

你的问题是cl2-&gt;make_shared_from_this()前面的*:这将取消引用std::shared_ptr&lt;Client&gt;bind() 表达式看到 Client&amp; 并通过复制捕获 Client。删除* 应该可以解决问题。我还没有完全理解代码正在尝试做什么(我正在手机上阅读它)但我猜你实际上想要捕获std::shared_ptr&lt;Client&gt;而不是Client

另外,由于cl2 已经是std::shared_ptr&lt;Client&gt;,因此在指向的对象上调用make_shared_from_this() 是没有意义的。它只是重新创建了一个不必要的st::shared_ptr&lt;Client&gt;

【讨论】:

  • 谢谢!但我以前不知道将std::shared_ptr 作为类成员函数的第一个参数传递给std::bind 的能力。这对我来说是新事物。
  • @dronte7: std::bind(...) 指定使用std::invoke(...) (我认为这是处理函数参数的指向成员的特殊情况的函数的名称)可以处理任何可复制的类指针类型。它在这方面类似于std::mem_fn(...),也以std::invoke(...) 的形式指定。
  • 呵呵。有简答学校。然后是我对事情的看法 :) 我认为还有许多其他问题值得指出。
  • @sehe:在这段代码 sn-p 中肯定有更多的东西需要修复。但是,在手机上进行完整的代码审查有点挑战性......
  • 神圣的。我什至没有尝试通过手机在 SO 上发布任何内容 :)
【解决方案2】:

你试着去理解很好。但是,从简单开始!

  1. 您的复制构造函数未能调用基类复制构造函数 (!!) 哎呀
  2. 您的绑定绑定到
    • this(不会使共享指针保持活动状态)
    • *cl2-&gt;shared_from_this() - 哎呀,这绑定到*cl2 的副本按值¹。这显然是完成后 COPY 被破坏的原因

无效的引用是由 1. 和 2.b. 的组合引起的。

我建议简化。很多!

  1. 使用零规则(仅在需要时声明特殊成员。在此,您引入了一个错误,因为您在手动编写不需要的复制构造函数时做错了)
  2. 在适当的情况下使用非拥有引用(并非所有内容都需要是智能指针)
  3. 对于不需要共享所有权的事物,首选std::unique_ptr(共享所有权应该非常少见)

  4. 首选std::lock_guard,而不是手动锁定和解锁(这不是异常安全的)

  5. 许多其他人:work_guard 不需要复制,它已经有一个 reset() 成员,通过 const-reference 捕获,如果你要捕获,不需要在 @ 上使用 error_code 987654333@,在取消引用之前检查resolve 的端点迭代器,改用boost::asio::connect,这样您就不必检查和迭代不同的端点等。
  6. 如果您无论如何都要进行动态分配,首选std::string 而不是固定大小的缓冲区,使用非实现定义的计时持续时间(例如1.0s,而不是duration&lt;double&gt;(1)),考虑使用boost::thread_group 而不是@987654340 @ 或至少只加入 joinable() 等线程
  7. 建议使 async_connect 成为 Client 的一部分,这样您就可以像其他所有绑定一样简单地绑定成员(使用 shared_from_this(),而不是编写您遇到的错误)
  8. 如果您还有时间,可以考虑使用 Beast 来创建/解析 Http 请求和响应

作为参考,MyWorker 可能是这样的:

class MyWorker {
    ba::io_context& io_context;

  public:
    MyWorker(ba::io_context& _io_context) : io_context(_io_context) {}

    //// best is to not mention these at all, because even `default`ing can change what compiler generates
    //MyWorker(const MyWorker& mw) = default;
    //MyWorker(MyWorker&& mw) = default;
    //~MyWorker() = default;

    void operator()() {
        TRACE("Thread Start");

        while (true) {
            try {
                io_context.run();
                break; // normal end
            } catch (boost::system::system_error const& se) {
                TRACE("MyWorker: received an error: " << se.code().message());
            } catch (std::exception const& ex) {
                TRACE("MyWorker: caught an exception: " << ex.what());
            }
        }

        TRACE("Thread Finish");
    }
};

此时,您完全可以将其设为 lambda:

auto mw = [&io_context] {
        TRACE("Thread Start");

        while (true) {
            try {
                io_context.run();
                break; // normal end
            } catch (boost::system::system_error const& se) {
                TRACE("MyWorker: received an error: " << se.code().message());
            } catch (std::exception const& ex) {
                TRACE("MyWorker: caught an exception: " << ex.what());
            }
        }

        TRACE("Thread Finish");
    };

简单得多。

简化的完整代码

看看例如新连接代码的简单性:

void Start() {
    tcp::resolver resolver(io_context);
    ba::async_connect(sock,
        resolver.resolve({"unegare.info", "80"}),
        [self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}

在调用OnConnect 时打印端点。

Live On Coliru²

#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>

namespace ba = boost::asio;
using ba::ip::tcp;
using namespace std::literals;

static std::mutex s_stdout;
#define TRACE(expr) { \
    std::lock_guard<std::mutex> lk(s_stdout); \
    std::cout << "[" << std::this_thread::get_id() << "] " << expr << std::endl; \
}

class Client : public std::enable_shared_from_this<Client> {
    ba::io_context& io_context;
    tcp::socket sock;
    std::string buf;

  public:
    Client(ba::io_context& _io_context) : io_context(_io_context), sock{io_context}
    { }

    void Start() {
        tcp::resolver resolver(io_context);
        ba::async_connect(sock,
                resolver.resolve({"unegare.info", "80"}),
                std::bind(std::mem_fn(&Client::OnConnect), shared_from_this(), std::placeholders::_1));
    }

    void OnConnect(const boost::system::error_code& ec) {
        TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
        if (ec) {
            TRACE(__FUNCTION__ << ": " << ec.message());
        } else {
            buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
            TRACE(std::quoted(buf));
            sock.async_write_some(ba::buffer(buf),
               std::bind(std::mem_fn(&Client::OnSend), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
        }
    }

    void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
        if (ec) {
            TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
        } else {
            TRACE(__FUNCTION__);
            buf.assign(512, '\0');
            sock.async_read_some(ba::buffer(buf), std::bind(std::mem_fn(&Client::OnRecv), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
        }
    }

    void OnRecv(const boost::system::error_code& ec, std::size_t bytes_transferred) {
        TRACE(__FUNCTION__);
        if (ec) {
            TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
        } else {
            buf.resize(bytes_transferred);
            TRACE(std::quoted(buf));
        }
    }
};

int main() {
    ba::io_context io_context;
    auto work_guard = make_work_guard(io_context);

    boost::thread_group vth;

    auto mw = [&io_context] {
        TRACE("Thread Start");

        while (true) {
            try {
                io_context.run();
                break; // normal end
            } catch (boost::system::system_error const& se) {
                TRACE("MyWorker: received an error: " << se.code().message());
            } catch (std::exception const& ex) {
                TRACE("MyWorker: caught an exception: " << ex.what());
            }
        }

        TRACE("Thread Finish");
    };

    vth.create_thread(mw);

    try {
        std::make_shared<Client>(io_context)->Start();

        char t;
        std::cin >> t;
        work_guard.reset();
    } catch (std::exception const& ex) {
        TRACE("Main Thread: caught an exception: " << ex.what());
    }

    vth.join_all();
}

打印:

[140095938852608] Thread Start
[140095938852608] OnConnect ep:95.165.130.37:80
[140095938852608] "GET / HTTP/1.1
Host: unegare.info

"
[140095938852608] OnSend
[140095938852608] OnRecv
[140095938852608] "HTTP/1.1 200 OK
Date: Sun, 22 Dec 2019 22:26:56 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Sun, 10 Mar 2019 10:17:38 GMT
ETag: \"37-583bac3f3c843\"
Accept-Ranges: bytes
Content-Length: 55
Content-Type: text/html

<html>
<head>
</head>
<body>
It works.
</body>
</html>
"
q
[140095938852608] Thread Finish

奖金

std::bind 自 C++11 起已过时,请考虑改用 lambda。既然 Coliru 不想再合作了,我就把三个改动过的函数完整贴出来:

void Start() {
    tcp::resolver resolver(io_context);
    ba::async_connect(sock,
        resolver.resolve({"unegare.info", "80"}),
        [self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}

void OnConnect(const boost::system::error_code& ec) {
    TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
    if (ec) {
        TRACE(__FUNCTION__ << ": " << ec.message());
    } else {
        buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
        TRACE(std::quoted(buf));
        sock.async_write_some(ba::buffer(buf),
            [self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnSend(ec, bytes_transferred); });
    }
}

void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
    if (ec) {
        TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
    } else {
        TRACE(__FUNCTION__);
        buf.assign(512, '\0');
        sock.async_read_some(ba::buffer(buf), 
            [self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnRecv(ec, bytes_transferred); });
    }
}

¹Does boost::bind() copy parameters by reference or by value?

² Coliru 不允许网络访问

【讨论】:

  • Coliru 似乎已经停止工作,所以这里是 Wandbox 上的 lambda 化奖金:wandbox.org/permlink/dyQOZYTyEAwSxwHd
  • 如您所见,我将代码放在在线编译器上。你应该看到你有什么不同(我猜你忘记了成员声明中的&amp;)。
  • sock{io_context} 是“统一初始化语法”(c++11 起)(一种流行的用词不当),实际上只是调用像sock(io_context) 这样的构造函数。另见stackoverflow.com/questions/18222926/…
  • @dronte7: 使用lock_guardunique_lock 代替lock()/``unlock()`; endl 总是错的:使用’\n’ 如果你真的想刷新流flush;具有复制/移动 ctor 的类型应具有相应的分配;我没有看到全局对象的位置,甚至没有保护现有的全局对象 - 至少,包装在一个函数中以保证它在第一次使用之前被构造; catch by const&amp;(除非修改后重新抛出异常);代码块记录特殊成员多次重复:封装调用,捕获原因; ...
  • @dronte7: ...我看不出循环或其前面的reserve()的任何原因;而不是阅读char,我会使用cin.ignore();我会使用jthread (C++20) 或从thread 派生的类,其中join()s 在其析构函数中;我没有使用过 Boost ASIO,但我相信它的执行器复制起来很便宜,并且不需要为这些使用 shared_ptr,但我不是这方面的专家,可以肯定地说。解决这些问题(以及这些问题)可能会发现其他可疑代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多