使用 std::optional 在移动构造函数/赋值中使我的 RAII 对象无效

huangapple go评论71阅读模式
英文:

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. 将其设置为已知为无效的某个值(也许将类型更改为有符号整数并使用 -1 作为无效值)
  2. 使用类似 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:

  1. Set it to some value known to be invalid (maybe change the type to a
    signed int and use -1 as the invalid value)
  2. Use something like std::optional and set it to std::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&lt;std::uint32_t&gt;(-1);

    Session() :
        id(id_counter++)
        /* 分配资源 */
    {}
    Session(const Session&amp;) = delete;
    Session(Session&amp;&amp; other) noexcept :
        id(std::exchange(other.id, invalid_id)) // 用无效 id 进行交换
        /* 移动或交换资源 */
    {}
    Session&amp; operator=(const Session&amp;) = delete;
    Session&amp; operator=(Session&amp;&amp; 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 ids, 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&lt;std::uint32_t&gt;(-1);

    Session() :
        id(id_counter++)
        /* allocate the resource */
    {}
    Session(const Session&amp;) = delete;
    Session(Session&amp;&amp; other) noexcept :
        id(std::exchange(other.id, invalid_id)) // exchange with the invalid id
        /* move or exchange the resource */
    {}
    Session&amp; operator=(const Session&amp;) = delete;
    Session&amp; operator=(Session&amp;&amp; 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;
};

Demo

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&amp;) noexcept = default;
    constexpr engaged&amp; operator=(const engaged&amp;) noexcept = default;
    constexpr engaged(engaged&amp;&amp; other) noexcept : engaged_(std::exchange(other.engaged_, false)) { }
    constexpr engaged&amp; operator=(engaged&amp;&amp; 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 &lt;utility&gt;
#include &lt;cstdint&gt;
#include &lt;cstddef&gt;
#include &lt;cstdio&gt;
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] = &#39;\0&#39;;
}
~Session()
{
if ( m_buffer != nullptr )
delete[] m_buffer;
}
Session(const Session&amp;) = delete;
Session&amp; operator = (const Session&amp;) = delete;
Session(Session&amp;&amp; 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&amp; operator = (Session&amp;&amp; rhs) noexcept
{
if ( this != &amp;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( &quot;ses is valid\n&quot;);
else
std::printf( &quot;ses is NOT valid\n&quot;);
}

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

huangapple
  • 本文由 发表于 2023年6月1日 04:05:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76376894.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定