理论上,我应该能够使用自定义指针类型和删除器,以便让
unique_ptr
管理非指针的对象。我尝试了以下代码:
#ifndef UNIQUE_FD_H
#define UNIQUE_FD_H
#include <memory>
#include <unistd.h>
struct unique_fd_deleter {
typedef int pointer; // Internal type is a pointer
void operator()( int fd )
{
close(fd);
}
};
typedef std::unique_ptr<int, unique_fd_deleter> unique_fd;
#endif // UNIQUE_FD_H
这不起作用(gcc 4.7 带有
-std=c++11
参数)。它响应以下错误:
In file included from /usr/include/c++/4.7/memory:86:0,
from test.cc:6:
/usr/include/c++/4.7/bits/unique_ptr.h: In instantiation of 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = int; _Dp = unique_fd_deleter]':
test.cc:22:55: required from here
/usr/include/c++/4.7/bits/unique_ptr.h:172:2: error: invalid operands of types 'int' and 'std::nullptr_t' to binary 'operator!='
通过深入研究
unique_ptr
的定义,我可以看到两个阻碍它工作的问题。第一个似乎明显违反了标准,unique_ptr
的析构函数将“指针”(根据我的定义,是一个int)与nullptr
进行比较,以查看它是否已初始化或不是。这与它通过布尔转换报告的方式形成对比,布尔转换是将其与 "pointer()"
(未初始化的“指针”)进行比较。这就是我看到的错误的原因 - 整数无法与 nullptr
相比较。
第二个问题是我需要某种方法来告诉
unique_ptr
什么是未初始化的值。我希望以下代码片段能够工作:
unique_fd fd( open(something...) );
if( !fd )
throw errno_exception("Open failed");
为了使其工作,
unique_ptr
需要知道“未初始化值”是-1,因为零是有效的文件描述符。
这是
gcc
中的一个错误,还是我试图在这里做一些根本无法完成的事情?
Deleter::pointer
暴露的类型必须满足NullablePointer
要求。其中最主要的是,这个表达必须是合法的:Deleter::pointer p = nullptr;
。当然,nullptr
几乎是由它不能隐式转换为数字这一事实来定义的,因此这是行不通的。
您必须使用可以通过
std::nullptr_t
隐式构造的类型。像这样的东西:
struct file_desc
{
file_desc(int fd) : _desc(fd) {}
file_desc(std::nullptr_t) : _desc(-1) {}
operator int() {return _desc;}
bool operator ==(const file_desc &other) const {return _desc == other._desc;}
bool operator !=(const file_desc &other) const {return _desc != other._desc;}
bool operator ==(std::nullptr_t) const {return _desc == -1;}
bool operator !=(std::nullptr_t) const {return _desc != -1;}
int _desc;
};
您可以将其用作
Deleter::pointer
类型。
你能做一些像下面这样简单的事情吗?
class unique_fd {
public:
unique_fd(int fd) : fd_(fd) {}
unique_fd(unique_fd&& uf) { fd_ = uf.fd_; uf.fd_ = -1; }
~unique_fd() { if (fd_ != -1) close(fd_); }
explicit operator bool() const { return fd_ != -1; }
private:
int fd_;
unique_fd(const unique_fd&) = delete;
unique_fd& operator=(const unique_fd&) = delete;
};
我不明白为什么你必须使用
unique_ptr
,它是为了管理指针而设计的。
在 cppreference.com 找到答案。 查看示例代码:
void close_file(std::FILE* fp) { std::fclose(fp); }
...
{
std::unique_ptr<std::FILE, decltype(&close_file)> fp(std::fopen("demo.txt",
"r"),
&close_file);
if(fp) // fopen could have failed; in which case fp holds a null pointer
std::cout << (char)std::fgetc(fp.get()) << '\n';
}// fclose() called here, but only if FILE* is not a null pointer
// (that is, if fopen succeeded)
在vs2019中尝试过,有效! 还尝试了成员和 lambda:
文件测试.h:
class A
{
std::unique_ptr<std::FILE, std::function<void(std::FILE*)>> fp;
}
文件测试.cpp
void A::OpenFile(const char* fname)
{
fp = std::unique_ptr < std::FILE, std::function<void(std::FILE*)>>(
std::fopen(fname, "wb"),
[](std::FILE * fp) { std::fclose(fp); });
}
完整样本:
#ifdef _MSC_VER
#define _CRT_NONSTDC_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <io.h>
#else
#include <unistd.h>
#endif
#include <memory>
#include <fcntl.h>
template<auto nullvalue, auto delete_>
class unique
{
using T = decltype(nullvalue);
struct generic_delete
{
class pointer
{
T t;
public:
pointer(T t) : t(t) {}
pointer(std::nullptr_t = nullptr) : t(nullvalue) { }
explicit operator bool() const { return t != nullvalue; }
friend bool operator ==(pointer lhs, pointer rhs) { return lhs.t == rhs.t; }
friend bool operator ==(pointer lhs, T rhs) { return lhs.t == rhs; }
friend bool operator ==(T lhs, pointer rhs) { return lhs == rhs.t; }
operator T() const { return t; }
};
void operator()(T p)
{
delete_(p);
}
};
public:
using type = std::unique_ptr<struct not_used, generic_delete>;
};
int main()
{
using unique_fd = unique<-1, close>::type;
static_assert(sizeof(unique_fd) == sizeof(int), "bloated unique_fd");
unique_fd fd1(open("fd.txt", O_WRONLY | O_CREAT | O_TRUNC));
if (fd1.get() != -1)
write(fd1.get(), "hello\n", 6);
}
开源 Android 框架定义了一个 unique_fd 类,可能会满足您的需求:https://android.googlesource.com/platform/system/core/+/c0e6e40/base/include/android-base/unique_fd.h
使尼可波拉斯类更加通用:
template<class T=void*,T null_val=nullptr>
class Handle
{
public:
Handle(T handle):m_handle(handle){}
Handle(std::nullptr_t):m_handle(null_val){}
operator T(){return m_handle;}
bool operator==(const Handle& other) const
{return other.m_handle==m_handle;}
private:
T m_handle;
};
typedef Handle<int,-1> FileDescriptor;
typedef Handle<GLuint,0> GlResource; // according to http://stackoverflow.com/questions/7322147/what-is-the-range-of-opengl-texture-id
// ...
我不确定是否应该有默认模板参数值。
这个解决方案基于 Nicol Bolas 的回答:
struct FdDeleter
{
typedef int pointer;
void operator()(int fd)
{
::close(fd);
}
};
typedef std::unique_ptr<int, FdDeleter> UniqueFd;
它很短,但你必须避免将 UniqueFd 实例与 nullptr 进行比较并将其用作布尔表达式:
UniqueFd fd(-1, FdDeleter()); //correct
//UniqueFd fd(nullptr, FdDeleter()); //compiler error
if (fd.get() != -1) //correct
{
std::cout << "Ok: it is not printed" << std::endl;
}
if (fd) //incorrect, avoid
{
std::cout << "Problem: it is printed" << std::endl;
}
if (fd != nullptr) //incorrect, avoid
{
std::cout << "Problem: it is printed" << std::endl;
}
return 1;
我建议使用
shared_ptr
而不是 unique_ptr
来管理 int
句柄的生命周期,因为共享所有权语义通常更适合,并且删除器的类型被删除。您需要以下帮手:
namespace handle_detail
{
template <class H, class D>
struct deleter
{
deleter( H h, D d ): h_(h), d_(d) { }
void operator()( H * h ) { (void) d_(h_); }
H h_;
D d_;
};
}
template <class H,class D>
std::shared_ptr<H const>
make_handle( H h, D d )
{
std::shared_ptr<H> p((H *)0,handle_detail::deleter<H,D>(h,d));
return std::shared_ptr<H const>(
p,
&std::get_deleter<handle_detail::deleter<H,D> >(p)->h_ );
}
与文件描述符一起使用:
int fh = open("readme.txt", O_RDONLY); // Check for errors though.
std::shared_ptr<int const> f = make_handle(fh, &close);
理论上,我应该能够使用自定义指针类型和删除器,以便
管理非指针的对象。unique_ptr
不,你不应该。也就是说,就让它编译和运行而言,也许是这样,但你根本不应该使用
unique_ptr
来管理不是指针的东西。您绝对应该为您的资源编写适当的 RAII 类 - 例如操作系统文件描述符 - 或使用某个库中现有的此类。仅当您想要指向此类资源的指针时,unique_ptr 才有意义;但是,您不需要自定义删除器。