【问题标题】:C++: Not Understanding Object Destruction RulesC++:不了解对象破坏规则
【发布时间】:2014-08-12 01:39:07
【问题描述】:

以下 C++ 代码片段使用 Microsoft 的 C++ Rest SDK。我不明白为什么第一个片段有效而其他片段无效。我假设差异是由于对象破坏和范围规则造成的。我正在寻找关于为什么第一个片段有效而其他片段挂在 close() 上的解释。此外,SDK 可以做些什么来消除未来的错误。一些非常聪明的人看了片段,但从未发现问题。

第一个代码片段。该片段有效并完整显示。随后的代码片段替换其中的标记代码。请关注差异而不是其他干扰。通过在浏览器中发出单个 GET 请求并单步执行代码来测试代码。在所有情况下,request.reply() 只执行了一次。

    boost::lockfree::spsc_queue<web::http::http_request, boost::lockfree::capacity<1024>> queue;
web::http::experimental::listener::http_listener listener(U("http://localhost:3901"));
listener.support([&](web::http::http_request request)
{
    queue.push(request);
});
listener.open().wait();
std::cout << "listening ... hit enter to initiate shutdown." << std::endl;
std::getchar();
// BEGIN CODE IN QUESTION
while (!queue.empty())
{
    web::http::http_request request;
    if (queue.pop(request))
        request.reply(web::http::status_codes::ServiceUnavailable, U("Service Unavailable")).wait();
}
// END CODE IN QUESTION
listener.close().wait();

第二个代码片段。挂在 close() 上。

    // hangs on close().wait()
web::http::http_request request;
while (queue.try_pop(request))
{
    request.reply(web::http::status_codes::ServiceUnavailable, U("Service Unavailable")).wait();
}

第三个代码片段。挂在 close() 上。

    // hangs on close().wait(). Outer braces make no difference.
 {
    web::http::http_request request;
    while (queue.try_pop(request))
    {
        request.reply(web::http::status_codes::ServiceUnavailable, U("Service Unavailable")).wait();
        request.~http_request();
    }
}

第四个代码片段。挂在 close() 上。外大括号没有区别。

    // hangs on close().wait()
{
    web::http::http_request request;
    while (queue.try_pop(request))
    {
        request.reply(web::http::status_codes::ServiceUnavailable, U("Service Unavailable")).wait();
        request.~http_request();
    }
    request.~http_request();
}

更新:支持 Matt McNabb 的解释,如果我只发出一个 GET,则以下代码有效。我只是删除了循环来处理单个 GET。需要显式调用析构函数以避免挂起,但这是不正确的做法。

    web::http::http_request request;
requests.pop(request);
request.reply(web::http::status_codes::ServiceUnavailable, U("Service Unavailable")).wait();
    request.~http_request();

更新:循环后的显式析构函数调用使程序为单个 GET 工作。但是,两个或更多 GET 会引发异常。我不确定为什么。

    web::http::http_request request;
while (queue.try_pop(request))
{
    request.reply(web::http::status_codes::ServiceUnavailable, U("Service Unavailable")).wait();
}
request.~http_request();

【问题讨论】:

  • 所有行 request.~http_request(); 导致未定义的行为,应删除。 (从技术上讲,第一个没有,但是 UB 出现在第二个,或者当函数退出时)。为同一个对象调用两次析构函数或在调用析构函数后使用该对象是未定义的行为。
  • 1和2的区别是1销毁旧请求,每次都创建一个新请求;但是 2 重复使用相同的对象,而不会在每次循环迭代时破坏或创建它。如果您发布try_pop 的定义将会很有用,这样我们就可以看到它通过什么方式更新现有对象。也可能是web::http::http_request 不支持try_pop 使用的任何更新方法。
  • 这可能是一个愚蠢的问题,但try_pop 是什么?它没有出现在boost 1.55's interface documentation of spsc_queue
  • 重新。你的更新提到我的名字,去掉request.~http_request();。它会导致未定义的行为。 “C++ 的对象销毁规则”包括只允许调用一次析构函数。
  • 让我尽可能清楚地重复一遍:如果你显式调用堆栈上对象的析构函数,你将得到未定义的行为,因为析构函数将另外被第二次调用(自动)。不要那样做。相反,请关注为什么第二个示例不起作用,并尝试在每次循环迭代时构造一个新的请求对象,看看是否能解决问题。

标签: c++ scope destructor casablanca


【解决方案1】:

每个示例的问题似乎都是相同的:请求对象的构造和销毁不匹配。我很少见过如此巧妙地做错事。

简单的解决办法是:

  1. 在块内声明对象,此时将分配该对象。
  2. 不要显式调用析构函数(这不是一个好主意)。
  3. 方块退出时对象将被销毁。

看来需要在调用close()之前销毁,这是合理的。


我的意思是第二个例子是唯一一个对象根本没有被破坏的例子,它也失败了。我认为这是由于某些其他原因在此代码中不可见。

【讨论】:

  • 第二个例子是否暗示响应对象的复制或分配方法没有清理目标对象,因为它会导致挂起?这是怎么回事?
  • 不同的问题,没有足够的信息来推测。对于这个问答,只需确保您只销毁对象一次,最好是通过块退出而不是显式的 dtor 调用。
【解决方案2】:

免责声明:不了解该库,这是相当危险的

#include <iostream>
using namespace std;

class Obj {
public:
    Obj() {
        cout << "Constructor" << endl;
    }
    ~Obj() {
        cout << "Destructor" << endl;
    }
};

int main() {
    {
        Obj ea;
        ea.~Obj();
    }
    return 0;
}

输出:

Constructor 
Destructor 
Destructor

如果有要释放的资源或要执行的操作来清理请求,那么您在每种情况下都会造成很多麻烦,除非您执行以下步骤:

  1. 通过副本获取请求(poptry_pop
  2. 处理请求
  3. 一次性销毁和清理请求

在上面的片段中:

  1. 对象从队列中复制、处理和销毁。他们每一个人。 很好
  2. 对象被复制、处理但未销毁。 挂起
  3. 对象被复制、处理、销毁,然后再次销毁。 挂起
  4. 甚至比 3 更糟糕
  5. 对象被复制、处理和销毁。 对监听器来说很好,它可能会在函数作用域的末尾触发问题。
  6. 对象被复制、处理,其中只有一个正在调用析构函数以进行清理 - 因此只有一个请求。 一次请求即可

【讨论】:

  • 关于2,是的,后面的请求对象没有被销毁。除非响应对象超出范围,否则程序将挂起。
  • 导致原帖的问题已在 2014 年 7 月 1 日的 C++ Rest Sdk 开发分支中得到解决。