Non-Copyable类应该具有用户转换

问题描述 投票:1回答:1

我的问题是,非可复制对象是否具有隐式/显式用户转换?至少从我下面的例子来看,转换看起来非常像副本。

PS:我知道这里建议“避免隐式转换运算符”qazxsw poi

为了解释我的理由,假设我有一个使用RAII管理OS对象的类。没有什么花哨。

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c164-avoid-implicit-conversion-operators

哪个可以。它允许我做类似的事情:

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;
}

问题是其他OS功能不接受我的对象。所以我尝试了隐藏/显式用户转换的,总是危险的路径。

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

这允许我正常使用我的对象。

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

哪个太好了!但现在的问题是我允许泄漏闭合手柄的可能性。假设我直接将我的对象分配给HANDLE对象,从而不会泄漏它,不仅泄漏了非托管HANDLE,甚至泄漏了一个关闭的HANDLE。例如:

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

我知道问题1发生了以下情况:

1 - 我从操作系统获得了HANDLE并将其传递给我的对象来管理它; 2 - 我使用隐式用户转换运算符返回了HANDLE; 3 - 编译器调用我的对象析构函数来关闭句柄; 4 - 一个封闭的HANDLE以非显而易见的方式泄漏。可能的运行时错误甚至不会进行错误检查,因为HANDLE不是INVALID_HANDLE_VALUE。

当然,我也启用了此案例。但为了争论,让我们说我接受这个案子。

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

因为如果我选择了一元运算符,而不是选择隐式/显式用户转换,我可以避免从临时对象转换,例如:

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(...);
...

回到我的问题,这种情况是否可以避免?或者非可复制对象永远不应该有用户转换?说实话,它们与返回一些托管私有对象的简单get()有什么不同?也许是一种避免用户从临时对象转换的方法?我们可以在任何其他用途之前强制对象进行左值编辑,无论是方法调用还是转换?

c++ implicit-conversion lvalue noncopyable
1个回答
1
投票

这种情况可以避免吗?

不会。您正在使用的库函数不会,也不会,也不会支持您的智能资源管理。这是一种耻辱,特别是因为他们不提供任何自己的,但这是现实的情况。

或者非可复制对象永远不应该有用户转换?说实话,它们与返回一些托管私有对象的简单get()有什么不同?

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; } ... } 对读者来说是一个更大的警告标志,必须注意以“原始”方式提取的资源的生命周期。您的隐式转换更快更短,但不会发出信号,更有可能导致错误。

也许是一种避免用户从临时对象转换的方法?我们可以在任何其他用途之前强制对象进行左值编辑,无论是方法调用还是转换?

是的,成员函数可以有.get()。虽然如上所述,我认为这并不能解决你的首要问题,即无论你想出什么设计,每次使用这些库函数都需要读者仔细观察。

看看ref-qualifiers。它有一个std::unique_ptr函数(支持需要get()的第三方函数)并且没有任何隐含的转换(以防止这种情况偶然发生)。

我的一般建议是避免隐式转换,你可以帮助它。

© www.soinside.com 2019 - 2024. All rights reserved.