“*this = {}”在 CPP 中重置的成员函数内是否有效

cad*_*dam 4 c++ performance coding-style undefined-behavior

最近,我很不高兴地发现,每次我必须为对象创建“重置”函数时,我常常最终会重现构造函数的行为。

例子:

class Foo
{
    int i;
public:
    Foo() : i(42) {};
    void reset() {
        i = 42;
    }
};
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我想出了在reset函数中直接调用构造函数的想法,以确保行为相同。

修改后的例子:

class Foo
{
    int i;
public:
    Foo() : i(42) {};
    void reset() {
        *this = {};
    }
};
Run Code Online (Sandbox Code Playgroud)

这很有效,但我想到了几个问题,但还没有找到答案:

  • 这样做有效吗?这是UB吗?
  • 鉴于它涉及创建和复制对象,这样(不进行优化)效率是否会降低?
  • 您认为这是避免重复的好做法吗?或者还有另一种更一致的处理方式吗?

谢谢

dwt*_*wto 5

是的,没关系。

您正在做的是使用隐式定义的运算符=(由编译器免费提供,因为您自己没有定义)。

运算符 = 期望引用另一个 Foo 对象:

operator=(const Foo&)
Run Code Online (Sandbox Code Playgroud)

或者

operator=(Foo&&)
Run Code Online (Sandbox Code Playgroud)

您给它一个空列表初始化,它是为您创建的 Foo 完美定义的,因为未标记为显式的类构造函数是从给定的参数列表到由匹配构造函数构造的类的可能隐式转换的定义。

在您的情况下,由于您给出了一个空列表,因此您只需要求隐式转换为 Foo{},这意味着您希望使用以下代码:

*this = Foo{};
Run Code Online (Sandbox Code Playgroud)

这意味着将默认构造的 Foo 分配给我的对象,该对象与 i=42 一起生成。

默认的operator=会简单地将作为参数给出的对象按位复制到执行对象(*this)中。

所以在此之后,你会得到 this->i = 42。

它的定义很完美,但没有优化,它的效率比您的第一个版本低。然而,这是一种过早的优化,您需要先确定它没有经过优化,然后再选择更难以理解的代码。

您的第一个编码版本存在代码重复。在第二个版本中,您可以清楚地向其他程序员传达这是一个默认的 Foo,并且您将其重置为一个。

在这里了解如何在不进行优化的情况下使您的第一个版本更加高效并且需要更少的汇编指令。然而,即使使用最小的优化 -O1,它也会被优化。因此,除非有相关基准证明,否则我不会担心这一点。

如果证明代码效率低下,您可以通过最少的代码重复和更安全的编码模式来做到这一点(不易出现错误):

class Foo 
{       
    int m1 = m1_reset();
    int m2 = m2_reset();
    
    constexpr void internal_reset() { 
        m1 = m1_reset();
        m2 = m2_reset();
    }
    
    constexpr int m1_reset() { return 42; }
    constexpr int m2_reset() { return 84; }

public:
    constexpr Foo() = default;
    
    void reset() {
        internal_reset();
    }
};
Run Code Online (Sandbox Code Playgroud)
  • 在 C++20 中,使用 consteval 而不是 consexpr,尽管它不会对生成的机器代码产生影响。