代码之家  ›  专栏  ›  技术社区  ›  Stéphane

如何将lambda与模板std::unique_ptr一起使用?

  •  0
  • Stéphane  · 技术社区  · 4 年前

    NVIDIA TensorRT对象,例如 nvinfer1::IRuntime nvinfer1::ICudaEngine 不能直接存储在 std::unique_ptr<> 相反,他们有一个 destroy() 必须调用的方法。

    因此,要使其工作,您必须使用这样的删除器:

    #include <NvInfer.h>
    #include <cuda.h>
    
    template<typename T>
    struct NVIDIADestroyer
    {
        void operator()(T * t)
        {
            t->destroy();
        }
    };
    
    template<typename T>
    using NVIDIAUniquePtr = std::unique_ptr<T, NVIDIADestroyer<T>>;
    

    而不是 std::unique_ptr<T> ,然后使用 NVIDIAUniquePtr<T> .

    到目前为止,这工作得很好。然后,在清理代码时,我试图用lambda替换删除器,这样我就可以跳过定义 NVIDIADestroyer 结构。但我不知道该怎么做。我的想法大致是这样的:

    template<typename T>
    using NVIDIAUniquePtr = std::unique_ptr<T, [](T * t)
    {
        t->destroy();
    }>;
    

    但这会导致以下错误消息:

    TRT.hpp:52:45: error: lambda-expression in template-argument
    using NVIDIAUniquePtr = std::unique_ptr<T, [](T * t)
                                                 ^
    TRT.hpp:55:2: error: template argument 2 is invalid
      }>;
      ^
    

    有办法让这个工作吗?

    0 回复  |  直到 4 年前
        1
  •  5
  •   Maxim Egorushkin    4 年前

    使用定义为无状态的删除器 struct class 自C++11以来,它的运行时和空间开销为零,没有比这更好的了。

    使用函数模板而不是类模板作为删除器,不需要指定删除器类模板参数,也不必包含CUDA头文件。

    noexcept on deleter函数可能会导致调用代码变小。因为在调用者周围不需要编译器生成的堆栈展开代码 没有例外 电话。(GNU C++标准库 ~unique_ptr() 没有例外 无条件,但C++标准不要求这样做。GNU C++标准库可能正是出于我所说的原因为您做到这一点。太糟糕了 没有例外 出于ABI稳定性的原因(我们在C++中不能有好东西的第一个原因),编译器不会自动推断和应用它,理论上可以用用户提供的显式重写 没有例外 规范,但这本身就是一个大问题。)

    由于C++17,一个无捕获的lambda闭包也可以用作零开销的删除器:

    #include <memory>
    #include <iostream>
    
    // C++11
    struct Deleter { template<class P> void operator()(P p) noexcept { p->destroy(); } };
    template<class T> using P11 = std::unique_ptr<T, Deleter>;
    
    // C++17
    constexpr auto deleter = [](auto p) noexcept { p->destroy(); };
    template<class T> using P17 = std::unique_ptr<T, decltype(deleter)>;
    
    int main() {
        std::cout << sizeof(void*) << '\n';
        std::cout << sizeof(P11<void>) << '\n';
        std::cout << sizeof(P17<void>) << '\n';
    }
    

    编译于 -std=c++17 输出:

    8
    8
    8
    
        2
  •  0
  •   einpoklum    4 年前

    另一种方法是包装你奇怪的删除对象,使它们表现良好。也许是这样的:

    template <typename Wrapped>
    struct raiified : Wrapped {
    
        template <typename... Args>
        raiified(Args&&... args) : Wrapped(std::forward<Args>(args)...) { }
    
        raiified(const Wrapped& wrapped) : Wrapped(wrapped) { }
        raiified(Wrapped&& wrapped) : Wrapped(wrapped) { }
        raiified(const raiified& other) : wrapped(other.wrapped) { }
        raiified( raiifiedd&& other) : Wrapped(std::move(other.wrapped)) { }
    
        // operator overloads?
    
        ~raiified() { wrapped.delete(); }
    }
    

    有了它,你应该能够使用 std::unique_ptr<raiified<nvinfer1::IRuntime>> 或者:

    namespace infer {
    
    template <typename T>
    using unique_ptr = std::unique_ptr<raiified<T>>
    
    }
    
        3
  •  0
  •   vrqq    3 年前

    很难通过特定的TYPE存储lambda函数(闭包),比如 std::function ,但我们可以通过手动内存管理将其存储在堆区。

    请注意,C++标准并不能保证lambda函数可以简单地复制,因此我们不能直接对其进行memcpy,只能对其进行包装。

    参考:asio/experimental/detail/channel_service。hpp:try_receive

    请参阅下面的伪代码:

    
    class MyWrapper {
    
    MyWrapper(MyWrapper&) = delete;
    
    void *mem;
    
    template<typename T>
    void set(T&& token) { 
        mem = malloc(sizeof(token));
        new (mem) T(std::move(token));
    }
    
    template<typename T>
    void release() {
        ((T*)mem)->~T();
        free(mem);
    }
    
    }; //end class
    

    在构造/取消构造此结构之前,我们必须知道类型T,我们无法避免它,因为C++不支持运行时类型。但是,我们可以在没有 typename T ,通过将执行器绑定到此结构中。(比如std::bind,或者只是lambda函数的包装器)

        4
  •  -1
  •   Michaël Roy    4 年前

    以下是使用lambda函数作为删除器的正确语法,这是您最初的问题。

    #include <functional>
    #include <memory>
    
    template <typename T>
    using NVIDIAUniquePtr = std::unique_ptr<T, std::function<void(T*)>>;
    
    template <typename T, typename... Args>
    NVIDIAUniquePtr<T> make_nvidia_unique(Args&&... args) {
      return NVIDIAUniquePtr<T>(
          new T(std::forward<Args>(args)...), [](T* p) {
            p->destroy();
            delete p;
          });
    }
    

    但是,正如许多人所指出的那样,这增加了unique_ptr的大小。这对你来说可能很重要,也可能不重要,但有更好的方法。。。

    使用大小为0的删除器对象将为每个对象节省32个字节。删除器类中的模板化移动构造函数对于移动 unique_ptr<T> 转换为T基类的unique_ptr,这意味着任何生产代码都需要它们。

    马克西姆·叶戈鲁什金的解决方案很好,但并不完整。

    #include <iostream>
    #include <memory>
    #include <type_traits>
    
    template <typename T>
    struct nv_deleter {
      nv_deleter() noexcept = default;
      nv_deleter(nv_deleter&&) noexcept = default;
    
      template <typename U,
                typename = std::enable_if_t<std::is_convertible<U*, T*>::value>>
      nv_deleter(nv_deleter<U>&&) noexcept {}
    
      template <typename U,
                typename = std::enable_if_t<std::is_convertible<U*, T*>::value>>
      nv_deleter& operator=(nv_deleter<U>&&) noexcept {}
    
      // It is important that this does not throw.  If destroy() may throw, 
      // add a try/catch block.  
      void operator()(T* p) noexcept { 
        p->destroy();
        delete p;    // this is needed, if destroy() does not call delete, 
                     // you will end up with memory leaks 
      }
    };
    
    template <typename _T>
    using NVIDIAUniquePtr = std::unique_ptr<_T, nv_deleter<_T>>;
    
    template <typename T, typename... Args>
    auto make_nvidia_unique(Args&&... args) {
      return NVIDIAUniquePtr<T>(new T(std::forward<Args>(args)...),
                                nv_deleter<T>{});
    }
    
    int main() {
      struct Base {
        virtual ~Base() noexcept {}
        virtual void destroy() noexcept {}  // it is preferable that this is noexcept,
                                            // but you may not have any control 
                                            // over external library code 
      };
    
      struct Derived : Base {};
    
      NVIDIAUniquePtr<Derived> pDerived = make_nvidia_unique<Derived>();
    
      // this is the case where the templated move constructors are necessary. 
      NVIDIAUniquePtr<Base> pBase = std::move(pDerived);
    
      std::cout << sizeof(pDerived) << '\n';  // prints sizeof(void*) -> 8
      std::cout << sizeof(pBase) << '\n';     // prints sizeof(void*) -> 8
    
      return 0;
    }