英文:
Using std::optional to invalidate my RAII object in move constructor/asignment
问题
以下是您要翻译的代码部分:
class Session {
public:
Session(); // 分配资源并生成唯一的 Session::id。
~Session(); // 释放资源
Session(const Session&) = delete;
Session& operator=(Session&) = delete;
private:
std::uint32_t id;
}
鉴于我不能允许复制,我想允许移动,因此我需要实现移动构造函数和移动赋值运算符,但我不确定应该如何处理移动实例的 Session::id
。
我可以选择:
- 将其设置为已知为无效的某个值(也许将类型更改为有符号整数并使用
-1
作为无效值) - 使用类似
std::optional
的东西,将其设置为std::nullopt
以使其无效。
这两个选项中的哪一个(如果有的话)是正确的?
英文:
Say I have an RAII class, instances of which should never be copied:
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;
}
Given that I can't allow copies, I'd like to allow moving, so I need to implement the move constructor and move-assignment operator but I'm not sure what I should do with Session::id
of the moved instance.
I can either:
- Set it to some value known to be invalid (maybe change the type to a
signed int and use-1
as the invalid value) - Use something like
std::optional
and set it tostd::nullopt
to invalidate it.
Which (if either) of those options is correct?
答案1
得分: 6
由于这可以在不使用std::optional
的情况下完成,只需牺牲掉 2<sup>32</sup> 可能的id
中的一个值,我会像std::string::npos
一样初始化你的id
类型的常量为 -1
。
示例:
class Session {
public:
// 用于表示无效 id 的常量:
static constexpr std::uint32_t invalid_id = static_cast<std::uint32_t>(-1);
Session() :
id(id_counter++)
/* 分配资源 */
{}
Session(const Session&) = delete;
Session(Session&& other) noexcept :
id(std::exchange(other.id, invalid_id)) // 用无效 id 进行交换
/* 移动或交换资源 */
{}
Session& operator=(const Session&) = delete;
Session& operator=(Session&& other) noexcept {
// 根据需要检查自赋值
std::swap(id, other.id);
// 交换资源
return *this;
}
~Session() {
if(id != invalid_id) { // 如果需要检查
/* 释放资源 */
}
}
private:
inline static std::uint32_t id_counter = 0;
std::uint32_t id;
};
另一种选择是,如果在你的情况下更自然的话,可以将 0
设置为 invalid_id
。只需将invalid_id
初始化为 0
并将 id 计数更改为 id(++id_counter)
。
英文:
Since this can be done without std::optional
by just sacrificing one value out of the 2<sup>32</sup> possible id
s, I'd do like std::string::npos
and initialize a constant of the unsigned type your id
has with -1
.
Example:
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;
};
An option would be to let 0
be the invalid_id
if that would feel more natural in your case. You'd just have to initialize invalid_id
with 0
and change the id counting to id(++id_counter)
.
答案2
得分: 2
std::optional
因为moved from的optional并不被解除关联而不太合适。 (示例)
因为其默认的移动行为不符合所需的语义,仍然需要提供自定义的移动操作,并且std::optional
的价值不会很大。
可以编写一个类似optional的类型,该类型具有所需的移动操作。这种方法的一个优点是可以将默认的未使用值指定为模板参数。这种方法的一个缺点是类可能有多个成员,它们都可能有效或无效。因此,在更正移动操作后,使用类似optional的类型将导致将所有类状态移动到另一种类型,这是一种相当大的负担。
另一方面,可以轻松编写一个类似标志的类,用于指示实例是否已经moved from。
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类型通常不仅仅包含一个整数。通常也不会有很多这种类型。它是否已经moved from可能并不重要。总的来说,我会关注要编写哪些RAII类型以及每个类型的适当接口。
英文:
std::optional
is a poor fit because a moved from optional is not disengaged. (example)
Because its default move behavior doesn't match the desired semantics, one would still have to provide custom move operations and the std::optional
would not be of much value.
One could write an optional-like type which has the desired move operations. One advantage of this approach is that one could specify the default unused value as a template parameter. One disadvantage of this approach is that classes have more than one member and they are likely all valid or invalid. Therefore, using an optional-like type, after correcting move operations, will lead to moving all class state to another type. That is quite a burden to impose.
On the other hand, one could easily write a flag-like class that provides the indication of whether the instance has been moved from.
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;
};
(example)
> How would you do it?
There are trade-offs. An RAII type typically has more than a single integer. There are also usually not many such types. It may not matter whether it has been moved from. Generally speaking, I would focus on which RAII types to write and the appropriate interface for each.
答案3
得分: 1
我只会选择std::optional
方法,如果该类只有一个成员。因此,在这种情况下,拥有一个用于指示类实例有效性的bool
成员似乎是可以的。
使用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");
}
注意:如果将m_id
的类型更改为int
,也可以使用return m_id != -1;
来实现operator bool()
。然后将不需要额外的bool
成员。您可以简单地向类中添加一个成员变量,如inline static constexpr int invalid_id { -1 };
,然后在operator bool()
内部将m_id
与其进行比较。
英文:
I would only choose the std::optional
approach if the class had a single member. So in this case, having a bool
member for indicating the validity of an instance of the class seems fine.
Using the bool
member you can implement an overloaded operator bool()
for the class. This way, expressions like if (session_instance)
are possible (instead of writing if (session_instance.id != -1)
or if (session_instance.id.has_value())
, etc).
An example:
#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");
}
Note: The operator bool()
can also be implemented with return m_id != -1;
in case of changing the type of m_id
to int
. Then no extra bool
member will be required. You can simply add a member variable to your class like inline static constexpr int invalid_id { -1 };
and compare m_id
with it inside the operator bool()
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论