Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/150.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 不可复制类是否应具有用户转换_C++_Implicit Conversion_Lvalue_Noncopyable - Fatal编程技术网

C++ 不可复制类是否应具有用户转换

C++ 不可复制类是否应具有用户转换,c++,implicit-conversion,lvalue,noncopyable,C++,Implicit Conversion,Lvalue,Noncopyable,我的问题是,不可复制对象是否应该进行隐式/显式用户转换?至少在我下面的例子中,转换看起来非常像副本 PS:我知道这里建议“避免隐式转换运算符” 为了解释我的基本原理,假设我有一个使用RAII管理OS对象的类。没什么特别的 struct unique_handle { unique_handle(HANDLE h) : handle(h) {} //NON COPYABLE unique_handle(const unique_handle&) = delete;

我的问题是,不可复制对象是否应该进行隐式/显式用户转换?至少在我下面的例子中,转换看起来非常像副本

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

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

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 };
    }
}
HANDLE operator !(unique_handle& v2)
{
    return v2.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
        ...);
...
太棒了!但现在的问题是,我允许一个封闭的把手泄漏的可能性。假设我直接将我的对象分配给一个句柄对象,而不是将其赋值,这样不仅会泄漏非托管句柄,甚至还会泄漏一个闭合句柄。例如:

Problem 1
...
HANDLE h = w32::CreateFile(...);
...
我知道问题1发生了以下情况:

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

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

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, ...);
...
因为如果我没有选择隐式/显式用户转换,而是选择了一元运算符,我可以避免从临时对象进行转换,例如:

namespace w32
{
    unique_handle CreateFile(...)       
    {
        HANDLE handle = ::CreateFileA(...);
        return { handle };
    }
}
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;
    }
    ...
}
这是理想的,但据我所知,我们不能做同样的转换

struct unique_handle
{
...
    operator HANDLE() const noexcept { return 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;
    }
    ...
}
那么回到我的问题,这种情况可以避免吗?或者不可复制的对象不应进行用户转换?老实说,它们与返回一些托管私有对象的简单get()有何不同? 也许是避免用户从临时对象转换的一种方法?我们是否可以在任何其他用途之前强制对象左值化,无论是方法调用还是转换

这种情况可以避免吗

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

或者不可复制的对象不应进行用户转换?老实说,它们与返回一些托管私有对象的简单get()有何不同

.get()
是一个更大的警告标志,提醒读者必须注意以“原始”方式提取的资源的生命周期。您的隐式转换写起来更快、更短,但不表示这一点,更可能导致错误

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

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

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


我的一般建议是,在可以帮助的地方避免隐式转换。

Ref限定符确实做到了这一点。我在这里尝试了两种方法:操作符句柄()&{return HANDLE;}操作符句柄()&&{return release();}和[[nodiscard]]HANDLE get()&{…}[[nodiscard]]HANDLE release(){…}如果它们是好的路径或不是好的路径,则需要考虑一下。。。