在使用具有 std::expected 返回的工厂函数时,如何避免两次调用析构函数,而无需运行时成本

How to avoid calling the destructor twice when using a factory function with a std::expected return without runtime costs

提问人:Sickeroni 提问时间:6/12/2023 最后编辑:marc_sSickeroni 更新时间:7/10/2023 访问量:143

问:

这是关于嵌入式 C++。假设我有一个结构体。Timer_t

创建新对象

  • 构造函数是私有的
  • 我们有一个工厂功能作为公共成员makeTimer()

我们不能在设备上使用异常,为了初始化计时器,我们可能会遇到错误。

这就是为什么

  • 默认构造函数对用户是隐藏的
  • 使用工厂函数
  • 工厂函数返回std::expected<Timer_t,Error_t>

由于我们使用 C++,人们往往会失败(经常在这里叫我)

  • 构造函数的初始化功能很强大
  • 析构函数在反初始化方面功能强大

构造函数在这里只有一半可用,工厂函数就是这个。

对于析构函数,它工作得很好。如果我们离开范围,我们会取消初始化它。这就是它应该的样子。

现在问题开始了:如果我们在 Object 中返回,我们就会移动。makeTimer()

更准确地说,我们称之为 !move constructor

因此,我们有 2 个对象,对于该对象,我们称之为析构函数。

更准确地说:

makeTimer() -> Timer_t() -> std::move/Timer_t(Timer_t &&) -> ~Timer_t() ->  program ends -> ~Timer_t(); 

对于移动,这是预期的行为。因此,它符合标准,但很烦人。

在嵌入式的上下文中,我看到人们在扩展代码时会在这里失败的风险很大。

我只想在最后调用析构函数一次。

  • 如果我使用Timer_t作为回报,它就可以了!(这就是令人沮丧的地方)
  • 我禁止使用非平凡的类型(呃!或者我从来没有看到过这个特定的计时器的好例子)
  • use(会扼杀std::expected背后的想法,并使代码不那么“好”)Error_t makeTimer(Timer_t & uninitialized Type)
  • 使用标志/计数器,例如(额外费用...)或简单的布尔值。std::shared_ptr

有没有更好更清洁的想法来解决这个问题?我不能是唯一一个拥有它的人。

C++ 嵌入式 移动语义 std-expected

评论

1赞 Yksisarvinen 6/12/2023
你确定有动作吗?(北)RVO 可能会根据您的实际代码对其进行优化。
0赞 Lundin 6/13/2023
为什么要破坏嵌入式系统中的计时器资源?这没有任何意义。它不是PC。
0赞 Sickeroni 6/15/2023
@Yksisarvinen解释之后不再了。但是 RVO 正在删除析构函数,这就是我想要的
0赞 Sickeroni 6/15/2023
@Lundin题外话...但要回答它。这在环境下是有道理的,在那里你有不同的操作模式(commisioning、setupmode等),这也是一些(!)PWM驱动器的第一步,可用于闪烁或褪色的LED。

答:

7赞 Jan Schultke 6/12/2023 #1

多亏了 RVO(返回值优化),您描述的问题在 C++ 中并不存在。请考虑以下代码:

#include <expected>

struct Timer_t {
    Timer_t();
    Timer_t(Timer_t&&);
    Timer_t& operator=(Timer_t&&);
    ~Timer_t();
};

struct Error_t {
    Error_t();
    Error_t(Error_t&&);
    Error_t& operator=(Error_t&&);
    ~Error_t();
}; 

std::expected<Timer_t, Error_t> makeTimer() {
    return {};
}

int main() {
    auto timer = makeTimer();
}

如果没有 RVO,看起来首先必须创建一个默认构造的 ,它将调用 ,然后调用移动构造函数,因为右侧是 prvalue。return {}std::expectedTimer_t()auto timer = makeTimer();

但是,自 C++17 以来,它的工作方式就不是这样了:

  • auto timer = makeTimer()受复制省略的影响,因此不会调用任何移动构造函数。
  • 在 中,由于 是与返回类型类型相同的 prValue,因此它受 RVO 的约束。return {};{}

这两项优化都是强制性的,因此可以保证我们只调用一次,然后在超出范围时调用。正如预期的那样,这是编译器的输出:Timer_t()~Timer_t()timer

main:
        sub     rsp, 24
        lea     rdi, [rsp+14]
        call    Timer_t::Timer_t() [complete object constructor]
        lea     rdi, [rsp+14]
        mov     BYTE PTR [rsp+15], 1
        call    Timer_t::~Timer_t() [complete object destructor]
        xor     eax, eax
        add     rsp, 24
        ret

请参阅使用 GCC 13 和 -std=c++2b -O2 的实时示例

评论

0赞 Sickeroni 6/12/2023
谢谢!快速,非常精确的完美答案!