【问题标题】:RAII, unique_ptr, and out parametersRAII、unique_ptr 和 out 参数
【发布时间】:2014-03-12 20:13:58
【问题描述】:

我是一名尝试学习 C++11 的 C# 开发人员。我正在尝试使用 windns.h 查询 DNS。

我从DnsQuery() 开始,读到我需要使用DnsRecordListFree() 释放结果记录输出参数。 C# 方法可能是使用try-finally 块来确保无论如何我都释放资源。

但我了解到没有 finally 块,windns.h 确实应该与时俱进并实现符合 RAII 的接口(据我了解典型建议)。我没有等待这种情况发生,而是尝试创建一个 RAII 包装类,其析构函数调用 DnsRecordListFree() 并使用运算符重载强制转换来获取原始指针。

但我对如何正确使用此句柄或指针来获取输出参数感到困惑。在我研究的同时,我了解到unique_ptr(我已经了解了一点)如何与自定义删除器一起使用。

到目前为止,这是我的简单代码。可能还有比这更多的错误,但我想我可以声明另一个 PDNS_RECORD *presult 并将其用作 out 参数,然后复制或移动或以其他方式将其值分配给 unique_ptr,但这听起来工作量太大/混乱。

在我看来,unique_ptr 的内部指针应该初始化为NULL,我应该能够以某种方式将指针的地址传递给 out 参数,DNSQuery 将更新原始值,并且当unique_ptr 超出我的函数范围时,DnsRecordListFree() 调用将自动进行。我不知道要找出正确/安全使用最少的正确组合。

#include <iostream>
#include <fstream>
#include <memory>
#include <Windows.h>
#include <WinDNS.h>

using namespace std;

auto pdnsDeleter = [&](PDNS_RECORD *ptr){ if (ptr) DnsRecordListFree(ptr); };

int main(int argc, char **argv)
{
    cout << "Hello World\n";

    std::unique_ptr<PDNS_RECORD*, decltype(pdnsDeleter)> results(0, pdnsDeleter);

    if (DnsQuery(L"google.com", DNS_TYPE_A, DNS_QUERY_STANDARD, NULL, ??results??, NULL))
    {
        cout << "google.com -> " << ??results??;
    }

    cout << "Done\n";
    getchar();

    return 0;
}

谢谢!

【问题讨论】:

  • Microsoft API 通常被编写为与 C 兼容而不是 C++,因此它们没有 RAII。包装是要走的路。
  • @MarkRansom 我不确定我是否理解你。您在 RAII 和“包装”之间有什么区别? RAII 是“包装”资源以控制其生命周期和销毁的模式或实践,不是吗?
  • @IronSavior 通过“包装”我的意思是将本机类型放在另一个包装类中。就像你说的那样。
  • @IronSavior 在您所描述的上下文中使用时,Mark 正在讨论它通常称为 janitor 类的内容,清理并在出门时关掉灯门。与成熟的库智能指针相比,它们的构思要简单得多,后者提供了许多其他功能,包括复制保护、共享和引用计数等。

标签: c++ winapi dynamic-memory-allocation out-parameters


【解决方案1】:

您可以花一整天时间尝试调整标准智能指针,也可以自己编写。它们并不难做到,特别是如果您愿意作弊并允许访问原始指针本身。

struct DnsRAII
{
    PDNS_RECORD p;

    DnsRAII() : p(NULL) { }
    ~DnsRAII() { if (p != NULL) DnsRecordListFree(p, DnsFreeRecordList); }
};

DnsRAII results;
if (DnsQuery(L"google.com", DNS_TYPE_A, DNS_QUERY_STANDARD, NULL, &results.p, NULL))
// ...

【讨论】:

  • 隐式拷贝构造函数和拷贝赋值呢?
  • @MooingDuck,我想你应该创建一个私有的复制构造函数和赋值运算符,这样它们就不能被使用。否则这会变得更加复杂。
  • 好的,这很有帮助,但对我来说这似乎太疯狂了,处理输出参数是如此困难!这种情况在 WinAPI 和其他库中肯定会出现很多情况,肯定会受益于 finally 块,而不是所有这些额外的代码和约定,对吧?
  • @uosɐſ 按照 C++ 标准,这并不难。通常,一旦你创建了一个这样的类,你就会一遍又一遍地使用它,开销只会产生一次——它实际上比使用站点上的 finally 块更简单。
【解决方案2】:

除非我遗漏了一些东西(我没有方便的 Windows 框来编译它,所以请原谅我)你的代码是不正确的。就我个人而言,我不会用智能指针来做这件事,因为你基本上使用它的只是一个自定义的看门人类(你可以编写一个足够简单的类)。

无论如何,首先,您的删除者应该是:

auto pdnsDeleter = [&](PDNS_RECORD ptr){ if (ptr) DnsRecordListFree(ptr, DnsFreeRecordList); };

接下来,你的智能指针的类型应该是:

std::unique_ptr<DNS_RECORD, decltype(pdnsDeleter)> results;

最后,我相信你的这个智能指针的loadup应该是在确定函数成功之后:

PDNS_RECORD pdnsrec;
if (DnsQuery(L"google.com", DNS_TYPE_A, DNS_QUERY_STANDARD, NULL, &pdnsrec, NULL))
{
    results.reset(pdnsrec);
}

如果我做对了,您的删除器将在获取链上的范围退出时正确触发。但同样,对于您可以使用自己的 janitor 类有效简化的工作来说,这似乎是一项非常艰巨的工作。

【讨论】:

  • 谢谢!那么这些选项比 finally 块的可能性更好吗?您所写的内容似乎确实比完整的看门人类实现稍微好一点,也更短一些。也许您可以演示更简单的代码?
  • @uosɐſ 这几乎正是 Mark 所展示的,这就是为什么我没有在这里真正做到这一点。做完整的 RAII 涉及更多,但要求列表也是如此。一种只知道 不是面向 C++ 的东西将被清理的方式是您似乎想要的最终目标,并且两者都会做到。智能指针增加了这一点,(例如独特的防止复制的能力,或共享的引用计数的能力等)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-26
  • 2012-08-14
  • 2012-08-08
  • 1970-01-01
  • 2019-12-28
  • 1970-01-01
  • 2011-07-20
相关资源
最近更新 更多