【问题标题】:swap std::unique_ptr with lambda as deleter -- GCC用 lambda 作为删除器交换 std::unique_ptr -- GCC
【发布时间】:2013-07-03 19:42:24
【问题描述】:

我们可以使用 lambda 作为带有 std::unique_ptr 的删除器吗?实际上,我是用 clang++ 做到的,而且很高兴这样做。

我正在使用std::swap 交换到std::unique_ptr<ObjType, decltyp(deleter)>;,其中auto deleter = [](struct addrinfo* ptr){if (ptr != nullptr) {freeaddrinfo(ptr);} };。 Clang 的交换似乎不需要复制赋值运算符,但 gcc 的 std::swap 需要,正如您在这些日志中看到的那样:

In file included from /usr/include/c++/4.8.1/memory:81:0,
                 from /home/zenol/proj/src/PROJ/TCPClient.cpp:28:
/usr/include/c++/4.8.1/bits/unique_ptr.h: In instantiation of ‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(std::unique_ptr<_Tp, _Dp>&&) [with _Tp = addrinfo; _Dp = Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0]’:
/usr/include/c++/4.8.1/bits/move.h:176:11:   required from ‘void std::swap(_Tp&, _Tp&) [with _Tp = std::unique_ptr<addrinfo, Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0>]’
/home/zenol/proj/src/Proj/SocketHelp.hpp:109:50:   required from ‘void Proj::retrieve_addresses(std::string, int, addrinfo&, addrinfo*&, T&, U) [with T = std::unique_ptr<addrinfo, Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0>; U = Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0; std::string = std::basic_string<char>]’
/home/zenol/proj/src/PROJ/TCPClient.cpp:65:49:   required from here
/usr/include/c++/4.8.1/bits/unique_ptr.h:193:16: erreur: use of deleted function ‘Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0& Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0::operator=(const Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0&)’
  get_deleter() = std::forward<deleter_type>(__u.get_deleter());
                ^
/home/zenol/proj/src/Proj/TCPClient.cpp:56:21: note: a lambda closure type has a deleted copy assignment operator
     auto deleter = [](struct addrinfo* ptr)
                     ^

标准是什么?我可以设法交换这两个 std::unique_ptr 吗?它们是一种解决方法吗? (也许将 lambda 封装在 std::function 中?...)

编辑: 这是一个应该或多或少相同的小例子:

auto deleter = [](struct addrinfo* ptr)
{if (ptr != nullptr) {freeaddrinfo(ptr);} };

std::unique_ptr<struct addrinfo, decltype(deleter)>
resources_keeper(nullptr, deleter);

int main()
{
    decltype(resources_keeper) plouf1(nullptr, deleter);
    decltype(resources_keeper) plouf2(nullptr, deleter);

    std::swap(plouf1, plouf2);
    return 0;
}

错误:

In file included from /usr/include/c++/4.8.1/bits/stl_pair.h:59:0,
                 from /usr/include/c++/4.8.1/bits/stl_algobase.h:64,
                 from /usr/include/c++/4.8.1/memory:62,
                 from mini.cpp:1:
/usr/include/c++/4.8.1/bits/move.h: In instantiation of ‘void std::swap(_Tp&, _Tp&) [with _Tp = __lambda0]’:
/usr/include/c++/4.8.1/tuple:381:36:   required from ‘void std::_Tuple_impl<_Idx, _Head, _Tail ...>::_M_swap(std::_Tuple_impl<_Idx, _Head, _Tail ...>&) [with long unsigned int _Idx = 1ul; _Head = __lambda0; _Tail = {}]’
/usr/include/c++/4.8.1/tuple:382:35:   required from ‘void std::_Tuple_impl<_Idx, _Head, _Tail ...>::_M_swap(std::_Tuple_impl<_Idx, _Head, _Tail ...>&) [with long unsigned int _Idx = 0ul; _Head = addrinfo*; _Tail = {__lambda0}]’
/usr/include/c++/4.8.1/tuple:667:33:   required from ‘void std::tuple<_T1, _T2>::swap(std::tuple<_T1, _T2>&) [with _T1 = addrinfo*; _T2 = __lambda0]’
/usr/include/c++/4.8.1/tuple:1050:7:   required from ‘void std::swap(std::tuple<_Elements ...>&, std::tuple<_Elements ...>&) [with _Elements = {addrinfo*, __lambda0}]’
/usr/include/c++/4.8.1/bits/unique_ptr.h:269:21:   required from ‘void std::unique_ptr<_Tp, _Dp>::swap(std::unique_ptr<_Tp, _Dp>&) [with _Tp = addrinfo; _Dp = __lambda0]’
/usr/include/c++/4.8.1/bits/unique_ptr.h:484:7:   required from ‘void std::swap(std::unique_ptr<_Tp, _Dp>&, std::unique_ptr<_Tp, _Dp>&) [with _Tp = addrinfo; _Dp = __lambda0]’
mini.cpp:21:29:   required from here
/usr/include/c++/4.8.1/bits/move.h:176:11: erreur: use of deleted function ‘__lambda0& __lambda0::operator=(const __lambda0&)’
       __a = _GLIBCXX_MOVE(__b);
           ^
mini.cpp:9:17: note: a lambda closure type has a deleted copy assignment operator
 auto deleter = [](struct addrinfo* ptr)
                 ^
In file included from /usr/include/c++/4.8.1/bits/stl_pair.h:59:0,
                 from /usr/include/c++/4.8.1/bits/stl_algobase.h:64,
                 from /usr/include/c++/4.8.1/memory:62,
                 from mini.cpp:1:
/usr/include/c++/4.8.1/bits/move.h:177:11: erreur: use of deleted function ‘__lambda0& __lambda0::operator=(const __lambda0&)’
       __b = _GLIBCXX_MOVE(__tmp);
           ^

【问题讨论】:

  • 你能举个小例子说明你想做什么吗?我试过这个并没有收到错误:ideone.com/LKXz7z
  • @VaughnCato:虽然你没有交换唯一指针......
  • 我添加了一个简短的示例代码来重现它(我希望它重现相同的东西:))

标签: c++ gcc c++11 lambda unique-ptr


【解决方案1】:

这与unique_ptrtuple无关,您可以将错误简化为:

int main()
{
  auto deleter = []() { };
  auto del2 = deleter;
  deleter = static_cast<decltype(deleter)>(del2);
}

使用 Clang 编译但使用 G++ 失败,出现此错误:

t.cc: In function ‘int main()’:
t.cc:5:11: error: use of deleted function ‘main()::<lambda()>& main()::<lambda()>::operator=(const main()::<lambda()>&)’
   deleter = static_cast<decltype(deleter)>(del2);
           ^
t.cc:3:19: note: a lambda closure type has a deleted copy assignment operator
   auto deleter = []() { };
                   ^

最后一个 C++11 标准在 [expr.prim.lambda]/19 中说:

与 lambda 表达式关联的闭包类型具有已删除的 (8.4.3) 默认构造函数和已删除的复制赋值运算符。它具有隐式声明的复制构造函数 (12.8),并且可能具有隐式声明的移动构造函数 (12.8)。

所以类型是否可移动赋值取决于编译器。

【讨论】:

    【解决方案2】:

    扩展Jonathan Wakely's answer

    当你交换到unique_ptrs 时,你还必须交换它们的删除器。你看到的问题归结为:clang 可以交换两个相同类型的 lambda, gcc 不能(并且标准允许 Jonathan 引用它)。示范:

    #include <utility>
    
    int main() {
      auto f = [](){};
      auto g(f);
      std::swap(f, g);
    }
    

    此代码适用于 clang,但无法使用 gcc 编译。 (没关系。)

    这就是它发生的原因。


    我建议如下:

    #include <memory>
    #include <utility>
    
    struct addrinfo { };
    
    void freeaddrinfo(addrinfo* ) { }
    
    struct deleter {
      void operator()(struct addrinfo* ptr) {
        if (ptr != nullptr)
          freeaddrinfo(ptr);
      }
    };
    
    using resources_keeper = std::unique_ptr<struct addrinfo, deleter>;
    
    int main() {
    
        resources_keeper plouf1(nullptr);
        resources_keeper plouf2(nullptr);
    
        std::swap(plouf1, plouf2);
        return 0;
    }
    

    请注意,代码也变得更简洁、更易读。


    如果您绝对必须使用 lambdas 解决这个问题,那么也许您可以尝试像这样 hackish 的方法:只交换指针而不交换删除器。

    #include <iostream>
    #include <memory>
    #include <utility>
    
    using namespace std;
    
    template <class T, class D>
    void swap_pointers_but_not_deleters(unique_ptr<T,D>& x, unique_ptr<T,D>& y) noexcept {
    
      T* x_ptr = x.release();
    
      x.reset(y.release());
    
      y.reset(x_ptr);
    }
    
    int main() {
    
      auto deleter = [](int* p){ delete p; };
    
      unique_ptr<int,decltype(deleter)> a(new int(1),deleter);
    
      unique_ptr<int,decltype(deleter)> b(new int(2),deleter);
    
      swap_pointers_but_not_deleters(a, b);
    
      cout << "a = " << *a << ", b = " << *b << endl;
    }
    

    虽然这段代码似乎可以工作,但我真的不喜欢它。我建议第一个不使用 lambdas 的解决方案。

    【讨论】:

    • 如您所说,仅“手动”交换指针是最糟糕的解决方案。我发现在“语义上”使用 lambda 更好,从某种意义上说,它“意味着”您正在构建一种函数,而不是使用关键字“struct”来构建仿函数(尽管 lambda 可以(是?)实现为一种特定的类型函子)。
    • 另一种解决方案是将 lambda 存储在 std::function 中。然后,std::functions 是可交换的。喜欢:std::function&lt;void(int*)&gt; deleter = [](int*p){ if (p != nullptr) free(p); };
    • @user2535207 一般来说,我也更喜欢 lambdas 结构。然而,使用哪一个也取决于上下文。如果我不打算重用它(不要创建可能很多实例),我会使用 lambda,如果我重用它,我会使用 struct。在您的情况下,您正在重新使用它,所以 在我看来 结构在您的情况下更好。但我必须强调,这是我个人的看法。至于表达你的意图,当我想要一个仿函数时,我不会太担心说struct:我们一直在遵循这种方法直到 C++11,所以这是常识,任何(相当熟练的)开发人员都会得到它.
    • @user2535207 事实证明,你可以用 lambdas 做到这一点!请检查我的新答案。
    【解决方案3】:

    我可以使用以下代码重现类似的错误:

    struct A
    {
        A() = default;
        A(A&&) = default;
        //A & operator=(A&&) = default;
        A(A const & ) = delete;
    };
    
    int main()
    {
        A a, b;
        std::swap(a,b);
    }
    

    取消注释移动赋值运算符,错误消失。我猜gcc不允许lambas的移动分配(我使用的是4.7.2版)。将 lambda 更改为实际的函数或仿函数,应该没问题。

    【讨论】:

    • 很伤心。它是 GCC 错误还是“功能”? (即 c++11 说应该允许,还是什么都没说?)
    • 不幸的是,我无法访问标准来确定是否对 lambda 有任何此类限制,但我想不出为什么需要这样的限制。这可能只是 gcc 中的一个错误(或不完整的功能)。
    • @pelletjl,没有人以“我无法访问标准”为借口,当前的草案总是在左侧边栏上可用,isocpp.orgFDIS 几乎相同标准
    • 您的示例也因 clang 3.4(主干 182210)而失败。这与 lambdas 有什么关系?
    【解决方案4】:

    事实证明,你可以用 lambdas 解决它,只要它们可以转换为函数指针(lambdas 什么都不捕获)。

    #include <memory>
    #include <utility>
    
    struct addrinfo { };
    
    void freeaddrinfo(addrinfo* ) { }
    
    auto deleter = [](struct addrinfo* ptr) {
      if (ptr != nullptr)
        freeaddrinfo(ptr);
    };
    
    using resources_keeper = std::unique_ptr<struct addrinfo, void(*)(struct addrinfo*)>;
    
    int main() {
    
        resources_keeper plouf1(nullptr,deleter);
        resources_keeper plouf2(nullptr,deleter);
    
        std::swap(plouf1, plouf2);
        return 0;
    }
    

    但是,我仍然更喜欢带有结构的other solution。它可能是最有效的一种(由于内联),其次是此处介绍的解决方案。如果删除器的实现真的很简单,那么传递一个重量级的std::function 对我来说似乎有点矫枉过正。这些性能考虑是否重要,这是分析器的工作。

    【讨论】:

    • 因为在我的情况下,不会有速度问题,所以我将使用这个解决方案,它既简化了删除器类型,又允许在创建 std::unique_ptrs 时编写删除器。谢谢:)
    • @user2535207 很高兴我们找到了您喜欢的解决方案! :)
    猜你喜欢
    • 1970-01-01
    • 2019-02-10
    • 2013-03-30
    • 1970-01-01
    • 1970-01-01
    • 2021-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多