c++ - Non-Copyable 类是否应该有用户转换

标签 c++ implicit-conversion lvalue noncopyable

我的问题是,不可复制对象是否应该有隐式/显式用户转换?至少从我下面的示例来看,转换看起来非常像拷贝。

PS:我知道这里建议“避免隐式转换运算符”https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c164-avoid-implicit-conversion-operators

为了解释我的基本原理,假设我有一个使用 RAII 管理操作系统对象的类。没什么特别的。

struct unique_handle
{
    unique_handle(HANDLE h) : handle(h) {}

    //NON COPYABLE
    unique_handle(const unique_handle&) = delete;
    unique_handle& operator=(const unique_handle&) = delete;

    //MOVEABLE
    unique_handle(unique_handle&& other) : handle(other.handle)
    {
        other.handle = INVALID_HANDLE_VALUE;
    }   
    unique_handle& operator=(unique_handle&& other)
    {
        if (this != &other) {
            handle = other.handle;
            other.handle = INVALID_HANDLE_VALUE;
        }
        return *this;
    }

    ~unique_handle()
    {
        if(handle != INVALID_HANDLE_VALUE)
            ::CloseHandle(handle);
    }
private:
    HANDLE handle;
}

没关系。它允许我做类似的事情:

namespace w32
{
    unique_handle CreateFile(...)       
    {
        HANDLE handle = ::CreateFileA(...);
        return { handle };
    }
}

问题是其他操作系统功能不会接受我的对象。因此,我尝试了隐式/显式用户转换这一始终危险的路径。

struct unique_handle
{
...
    operator HANDLE() const noexcept { return handle; }
...
}

这让我可以正常使用我的对象。

...
auto dirHandle = w32::CreateFile(...); // my function returning my object
::ReadDirectoryChangesW(dirHandle, // my object on a native OS call
        ...);
...

太棒了!但现在的问题是我允许泄漏关闭句柄的可能性。假设我直接将我的对象分配给一个 HANDLE 对象,从不对其进行左值化,这不仅会泄漏非托管 HANDLE,甚至会泄漏一个已关闭的 HANDLE。例如:

Problem 1
...
HANDLE h = w32::CreateFile(...);
...

我了解到问题 1 发生了以下情况:

1 - 我从操作系统获取了 HANDLE 并将其传递给我的对象以对其进行管理;
2 - 我使用隐式用户转换运算符返回了 HANDLE;
3 - 编译器调用了关闭句柄的对象析构函数;
4 - 闭合的 HANDLE 以不明显的方式泄漏。可能的运行时错误。甚至错误检查也无法捕捉到这一点,因为 HANDLE 不是 INVALID_HANDLE_VALUE。

当然,我也启用了这种情况。但是为了争论起见,让我们说我接受这个案例。

Problem 2
HANDLE h1 = ...;
{
    auto h2 = w32::CreateFile(...); // my method and my object
    h1 = h2;                        // implicit cast to the OS HANDLE
}                                   // our favorite feature closing the HANDLE
::ReadDirectoryChangesW(h1, ...);
...

因为如果我不选择隐式/显式用户转换,而是选择一元运算符,我本可以避免从临时对象执行类似以下操作的转换:

HANDLE operator !(unique_handle& v2)
{
    return v2.handle;
}

这让我在以下几行出现编译错误:

...
HANDLE h = !w32::CreateFile(...);
...

这是理想的,但据我所知,我们不能对转化做同样的事情。

我想象的其他解决方案是函数,例如:

struct unique_handle
{
    ...
    // just return - In my opinion also a copy, but what can we make?
    [[nodiscard]] HANDLE get() const noexcept { return handle; } 

    // Stop managing this HANDLE
    [[nodiscard]] HANDLE release() noexcept
    {
        auto h = handle;
        handle = INVALID_HANDLE_VALUE;
        return h;
    }
    ...
}

那么回到我的问题,这种情况是可以避免的吗?或者 Non-Copyable 对象不应该有用户转换?老实说,它们与返回某个托管私有(private)对象的简单 get() 有何不同? 也许是一种避免用户从临时对象转换的方法?我们能否强制对象在任何其他用途之前被左值化,无论是方法调用还是转换?

最佳答案

is this scenario avoidable?

没有。您正在使用的库函数不会、不能也永远不会支持您的智能资源管理。这是一种耻辱,尤其是因为他们不提供自己的任何东西,但这是现实情况。

Or Non-Copyable objects should never have user conversion? And to be honest, how are they different from a simple get() that returns some managed private object?

.get() 对读者来说是一个更大的警告标志,必须注意以“原始”方式提取的资源的生命周期。您的隐式转换编写起来更快更短,但不要发出这种信号并且更有可能导致错误。

Maybe a way to avoid user conversions from temporary objects? Can we force a object to be lvalue-ed before any other use, be it a method call or a conversion?

是的,一个成员函数可以有ref-qualifiers .虽然,如上所述,我认为这并不能真正解决您的首要问题,即无论您提出什么设计,每次使用这些库函数都需要读者仔细观察。

查看 std::unique_ptr。它有一个 get() 函数(以支持需要 T* 的第三方函数)并且没有隐式转换为任何东西(以防止这种情况避免意外发生)。

我的一般建议是尽可能避免隐式转换。

关于c++ - Non-Copyable 类是否应该有用户转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54963695/

相关文章:

c++ - 链接 lib 时未解析的外部符号,编译器将字母 'A' 添加到函数名称

c++ - 为什么这不对转换构造函数进行隐式转换?

C++构造函数隐式转换没有发生

c++ - "Expression must be a modifiable LValue"

c++ - 未定义模板的隐式实例化 'class'

c++ - 跨平台 Flash Player 嵌入

c++ - 为什么我会收到 Wsign-conversion 警告?

c - 访问结构数组中结构的成员,该结构是不同结构数组的成员

c++ - 批准的避免左值转换警告和错误的方法?

c++ - 将一个类传递给 sprintf