std::unique_ptr custom deleters seem to need a pointer argument — is there an alternative that can take a non-pointer argument?

huangapple go评论65阅读模式
英文:

std::unique_ptr custom deleters seem to need a pointer argument -- is there an alternative that can take a non-pointer argument?

问题

以下是您要翻译的部分:

I was recently introduced to this mechanism of using a std::unique_ptr to implement a "generic" RAII mechanism:

// main.cpp

#include <memory>
#include <sys/fcntl.h>
#include <unistd.h>

#define RAII_CAT(x) raii ## x

#define RAII_CAT2(x) RAII_CAT(x)

#define RAII_FD(X) \
    std::unique_ptr<int, void(*)(int*)> RAII_CAT2(__LINE__){X, [](int* x){ if (-1 != *x) { close(*x); }}}

int main(int argc, char* argv[]) {
  {
    int fd = open("foo.txt", O_RDONLY);
    RAII_FD(&fd);
  }

end:
  return 0;
}

In the above code, the RAII_FD macro creates a std::unique_ptr object whose custom deleter takes an int* -- the pointer to a file-descriptor -- and calls close() on the file-descriptor.

I like this mechanism a lot, but I have a minor gripe that the custom deleter requires a pointer as its argument: aesthetically it feels a bit less than desirable.
E.g. in the code above, the custom deleter is a thin wrapper over int close(int) -- it would be nice, therefore, if the custom deleter could take an int instead of an int*...in which case perhaps a wrapper function wouldn't be necessary at all: perhaps a function pointer to int close(int) itself could be supplied.

I.e. variations of the following were tried, trying to register a custom deleter with signature void func(int) instead of void func(int*):

// main.cpp

#include <memory>
#include <sys/fcntl.h>
#include <unistd.h>

#define RAII_CAT(x) raii ## x

#define RAII_CAT2(x) RAII_CAT(x)

#define RAII_FD(X) \
    std::unique_ptr<int, void(*)(int)> RAII_CAT2(__LINE__){X, [](int x){ if (-1 != x) { close(x); }}}

int main(int argc, char* argv[]) {
  {
    int fd = open("foo.txt", O_RDONLY);
    RAII_FD(fd);
  }

end:
  return 0;
}

...the compile error was a vomit of stl errors that I perhaps don't grok 100%, but I think the gist is there are int*/int mismatches in the various template expansions.

Is there another similar mechanism by which one can implement a "generic" RAII mechanism with custom deleters whose argument doesn't necessarily need to be a pointer?

I'm open to learning about all possible solutions, but solutions that are actually usable in my target environment must be C++11 and non-Boost. Most preferable would be some similar "thin wrapper" over STL-native objects.

英文:

I was recently introduced to this mechanism of using a std::unique_ptr to implement a "generic" RAII mechanism:

// main.cpp

#include <memory>
#include <sys/fcntl.h>
#include <unistd.h>

#define RAII_CAT(x) raii ## x

#define RAII_CAT2(x) RAII_CAT(x)

#define RAII_FD(X) \
    std::unique_ptr<int, void(*)(int*)> RAII_CAT2(__LINE__){X, [](int* x){ if (-1 != *x) { close(*x); }}}

int main(int argc, char* argv[]) {
  {
    int fd = open("foo.txt", O_RDONLY);
    RAII_FD(&fd);
  }

end:
  return 0;
}

In the above code, the RAII_FD macro creates a std::unique_ptr object whose custom deleter takes an int* -- the pointer to a file-descriptor -- and calls close() on the file-descriptor.

I like this mechanism a lot, but I have a minor gripe that the custom deleter requires a pointer as its argument: aesthetically it feels a bit less than desirable.
E.g. in the code above, the custom deleter is a thin wrapper over int close(int) -- it would be nice, therefore, if the custom deleter could take an int instead of an int*...in which case perhaps a wrapper function wouldn't be necessary at all: perhaps a function pointer to int close(int) itself could be supplied.

I.e. variations of the following were tried, trying to register a custom deleter with signature void func(int) instead of void func(int*):

// main.cpp

#include <memory>
#include <sys/fcntl.h>
#include <unistd.h>

#define RAII_CAT(x) raii ## x

#define RAII_CAT2(x) RAII_CAT(x)

#define RAII_FD(X) \
    std::unique_ptr<int, void(*)(int)> RAII_CAT2(__LINE__){X, [](int x){ if (-1 != x) { close(x); }}}

int main(int argc, char* argv[]) {
  {
    int fd = open("foo.txt", O_RDONLY);
    RAII_FD(fd);
  }

end:
  return 0;
}

...the compile error was a vomit of stl errors that I perhaps don't grok 100%, but I think the gist is there are int*/int mismatches in the various template expansions.

Is there another similar mechanism by which one can implement a "generic" RAII mechanism with custom deleters whose argument doesn't necessarily need to be a pointer?

I'm open to learning about all possible solutions, but solutions that are actually usable in my target environment must be C++11 and non-Boost. Most preferable would be some similar "thin wrapper" over STL-native objects.

答案1

得分: 3

std::unique_ptr 其实并不仅仅在原生原始指针上运行。它支持满足 NullablePointer 要求 的所有类型。基本上,该类型应该在值语义和相等比较方面表现得像标量类型,并且可以通过 nullptr 成为一个独特的空状态,该状态也是值初始化状态。这要求你有一个表示空状态的状态,在你的情况下可以用 -1 来表示。

这样的类型将作为自定义删除器的类型成员 pointer 提供给 unique_ptr

// 代码未经测试!
// 你必须检查我没有犯愚蠢的错误或忘记某个要求!

struct Deleter {
    struct pointer {
        int fd = -1;

        pointer() noexcept = default;
        pointer(std::nullptr_t) noexcept {}

        // 应该明确声明
        // 但是这样的话无法直接将 `fd` 传递给 std::unique_ptr 构造函数
        pointer(int fd) noexcept : fd(fd) {};

        friend bool operator==(pointer p, pointer q) noexcept {
            return p.fd == q.fd;
        }

        // C++20 中 operator!= 是可选的
        friend bool operator!=(pointer p, pointer q) noexcept {
            return !(p == q);
        }

        operator bool() noexcept { return *this != nullptr; }
    };

    // 仅当 pointer 不表示空状态时,unique_ptr 调用此函数
    void operator()(pointer x) noexcept { close(x.fd); }
};

然后你可以这样使用:

std::unique_ptr<int, Deleter> u(fd);

实际上,元素类型是 int 并不重要。std::unique_ptr<double, Deleter> 同样可以工作。不过,你实际上不能使用 *u 进行解引用。如果需要这样做,那么你需要在 pointer 上也提供一个 operator*,在这种情况下,元素类型应该与其返回的类型匹配。

关于是否真的有用,考虑到大量样板代码和对 pointer 的略微不寻常的解释,这取决于你。

英文:

std::unique_ptr doesn't in fact operate only on native raw pointers. It supports all types satisfying the NullablePointer requirements. Basically the type is supposed to behave like a scalar type with respect to value semantics and equality comparison and be nullable with nullptr into a distinct null state that is also the value-initialized state. This requires that you have a state to represent the null state, which in your case can be served by -1.

Such a type would be provided to unique_ptr as a type member pointer of the custom deleter:

// Code not tested!
// You must check that I didn&#39;t make a stupid mistake or forgot a requirement!

struct Deleter {
    struct pointer {
        int fd = -1;

        pointer() noexcept = default;
        pointer(std::nullptr_t) noexcept {}

        // should probably be explicit
        // but then `fd` cannot be passed directly to the
        // std::unique_ptr constructor
        pointer(int fd) noexcept : fd(fd) {};

        friend bool operator==(pointer p, pointer q) noexcept {
            return p.fd == q.fd;
        }

        // operator!= is optional with C++20
        friend bool operator!=(pointer p, pointer q) noexcept {
            return !(p == q);
        }

        operator bool() noexcept { return *this != nullptr; }
    };

    // unique_ptr calls this only if pointer doesn&#39;t represent the null state
    void operator()(pointer x) noexcept { close(x.fd); }
};

Then you can use

std::unique_ptr&lt;int, Deleter&gt; u(fd);

in the way you intent it to. In fact it doesn't really matter that the lement type is int. std::unique_ptr&lt;double, Deleter&gt; would work as well. However, you can't actually dereference with *u. If that is required, then you need to give pointer a operator* as well and in that case the element type should match the type returned by it.

Whether this is really useful given the amount of boilerplate code and the somewhat unusual interpretation of pointer is up to you.

huangapple
  • 本文由 发表于 2023年2月19日 04:39:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/75496255.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定