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

Sic*_*oni 2 c++ embedded move-semantics std-expected

这是关于嵌入式 C++ 的。假设我有一个 struct Timer_t

创建一个新对象

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

我们不能在设备上使用异常,并且要初始化计时器,我们可能会收到错误。

这就是为什么

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

由于我们使用 C++,人们往往会失败(太频繁地叫我来这里)

  • 构造函数的初始化能力很强
  • 析构函数具有强大的去初始化能力

构造函数在这里只能使用一半,工厂函数对我们来说就是这样。

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

现在问题开始了:如果我们返回makeTimer()对象,我们就可以移动。

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

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

更准确地说:

makeTimer() -> Timer_t() -> std::move/Timer_t(Timer_t &&) -> ~Timer_t() ->  program ends -> ~Timer_t(); 
Run Code Online (Sandbox Code Playgroud)

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

在嵌入式环境中,我发现人们在扩展代码时会面临很大的失败风险。

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

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

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

Jan*_*tke 7

感谢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();
}
Run Code Online (Sandbox Code Playgroud)

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

然而,自 C++17 以来,它不再是这样工作的

  • auto timer = makeTimer()受到复制省略的影响,因此不会调用移动构造函数。
  • 在 中return {};,因为 是{}与返回类型具有相同类型的纯右值,所以它受RVO约束。

这两种优化都是强制性的,因此可以保证我们只调用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
Run Code Online (Sandbox Code Playgroud)

请参阅使用GCC 13 和-std=c++2b -O2.