提问人:John O'brien 提问时间:6/1/2023 最后编辑:Adrian MoleJohn O'brien 更新时间:6/1/2023 访问量:142
使用 std::optional 使我的 RAII 对象在移动构造函数/签名 [closed] 中失效
Using std::optional to invalidate my RAII object in move constructor/asignment [closed]
问:
假设我有一个 RAII 类,其实例永远不应该被复制:
class Session {
public:
Session(); // Allocates a resource and sets generates a unique Session::id.
~Session(); // Frees the resource
Session(const Session&) = delete;
Session& operator = (Session&) = delete;
private:
std::uint32_t id;
}
鉴于我不能允许复制,我想允许移动,所以我需要实现移动构造函数和移动赋值运算符,但我不确定我应该如何处理移动的实例。Session::id
我可以:
- 将其设置为某个已知无效的值(可能将类型更改为
signed int 并用作无效值)
-1
- 使用 类似 并将其设置为使其失效。
std::optional
std::nullopt
这些选项中哪一个(如果有的话)是正确的?
答:
由于这可以通过牺牲 232 个可能值中的一个值来完成,因此我确实希望并初始化一个 unsigned 类型的常量。std::optional
id
std::string::npos
id
-1
例:
class Session {
public:
// the constant used to signal an invalid id:
static constexpr std::uint32_t invalid_id = static_cast<std::uint32_t>(-1);
Session() :
id(id_counter++)
/* allocate the resource */
{}
Session(const Session&) = delete;
Session(Session&& other) noexcept :
id(std::exchange(other.id, invalid_id)) // exchange with the invalid id
/* move or exchange the resource */
{}
Session& operator=(const Session&) = delete;
Session& operator=(Session&& other) noexcept {
// check for self-assignment if needed
std::swap(id, other.id);
// swap the resource
return *this;
}
~Session() {
if(id != invalid_id) { // if the check is even needed
/* free the resource */
}
}
private:
inline static std::uint32_t id_counter = 0;
std::uint32_t id;
};
一种选择是让 be 如果这在您的情况下感觉更自然。您只需要初始化并将 id 计数更改为 .0
invalid_id
invalid_id
0
id(++id_counter)
只有当班级只有一个成员时,我才会选择这种方法。因此,在这种情况下,有一个成员来指示类实例的有效性似乎没问题。std::optional
bool
使用该成员,您可以实现类的重载。这样,表达式 like 是可能的(而不是写 or 等)。bool
operator bool()
if (session_instance)
if (session_instance.id != -1)
if (session_instance.id.has_value())
举个例子:
#include <utility>
#include <cstdint>
#include <cstddef>
#include <cstdio>
class Session {
public:
explicit Session(const std::uint32_t id, const std::size_t size = 10)
: is_valid { true }, m_id { id }, m_buffer { new char[size] }
{
m_buffer[size - 1] = '\0';
}
~Session()
{
if ( m_buffer != nullptr )
delete[] m_buffer;
}
Session(const Session&) = delete;
Session& operator = (const Session&) = delete;
Session(Session&& rhs) noexcept
: is_valid { std::exchange(rhs.is_valid, false) },
m_id { std::exchange(rhs.m_id, 0) },
m_buffer { std::exchange(rhs.m_buffer, nullptr) }
{}
Session& operator = (Session&& rhs) noexcept
{
if ( this != &rhs )
{
is_valid = std::exchange(rhs.is_valid, false);
m_id = std::exchange(rhs.m_id, 0);
if ( m_buffer != nullptr )
delete[] m_buffer;
m_buffer = std::exchange(rhs.m_buffer, nullptr);
}
return *this;
}
operator bool () const noexcept
{
return is_valid;
}
private:
bool is_valid;
std::uint32_t m_id;
char* m_buffer { nullptr };
};
int main( )
{
Session ses { 1000, 10 };
Session ses2 { 100, 20 };
ses2 = std::move( ses );
if ( ses )
std::printf( "ses is valid\n");
else
std::printf( "ses is NOT valid\n");
}
注意:如果将类型更改为 ,也可以实现 。那么就不需要额外的成员了。您可以简单地将成员变量添加到您的类中,例如并在 .operator bool()
return m_id != -1;
m_id
int
bool
inline static constexpr int invalid_id { -1 };
m_id
operator bool()
评论
if (session_instance.id != -1)” -
如果需要该运算符,则会出现,因此在这方面与用户的接口不会有所不同。operator bool () const noexcept { return id != invalid_id; }
if ( m_buffer != nullptr ) delete[] m_buffer;
delete[] m_buffer;
delete[]
nullptr
std::optional
是一个糟糕的拟合,因为从可选移出的不是脱离的。(示例)
由于其默认移动行为与所需的语义不匹配,因此仍必须提供自定义移动操作,并且没有多大价值。std::optional
可以编写一个类似可选的类型,该类型具有所需的移动操作。这种方法的一个优点是可以将默认的未使用值指定为模板参数。这种方法的一个缺点是类有多个成员,它们可能都有效或无效。因此,在更正移动操作后,使用类似可选的类型将导致将所有类状态移动到另一种类型。这是一个相当大的负担。
另一方面,可以很容易地编写一个类似标志的类,以指示实例是否已从中移动。
class engaged {
public:
constexpr engaged() noexcept = default;
constexpr engaged(const engaged&) noexcept = default;
constexpr engaged& operator=(const engaged&) noexcept = default;
constexpr engaged(engaged&& other) noexcept : engaged_(std::exchange(other.engaged_, false)) { }
constexpr engaged& operator=(engaged&& other) noexcept { engaged_ = std::exchange(other.engaged_, false); return *this; }
constexpr explicit operator bool() const noexcept { return engaged_; }
private:
bool engaged_ = true;
};
(例)
你会怎么做?
需要权衡取舍。RAII 类型通常具有多个整数。这样的类型通常也不多。它是否已移出可能无关紧要。一般来说,我会专注于编写哪些 RAII 类型以及每种类型的适当接口。
评论
bool
std::optional<std::uint32_t>
std::optional
std::optional
id
if(id != InvalidId)
if (!id.has_value())
std::optional
const
Session& operator = (const Session&) = delete;