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

Is "*this = {}" valid inside a member function for reset in CPP

提问人:cadam 提问时间:6/22/2023 更新时间:6/23/2023 访问量:141

问:

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

例:

class Foo
{
    int i;
public:
    Foo() : i(42) {};
    void reset() {
        i = 42;
    }
};

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

修改后的示例:

class Foo
{
    int i;
public:
    Foo() : i(42) {};
    void reset() {
        *this = {};
    }
};

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

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

谢谢

C++ 性能 编码样式 未定义行为

评论

1赞 sweenish 6/22/2023
调整构建器模式?
6赞 Marek R 6/22/2023
这完全没问题。在这个简单的情况下,假设规则接管,并且持久性不应有任何差异。对于更复杂的类型,可能会有显著差异。
2赞 ALX23z 6/22/2023
它没有错,但如果这是函数唯一做的事情,我宁愿不调用函数,而只是调用它来重置它。resetval = {};
3赞 NathanOliver 6/22/2023
只要你的类是默认可构造的,就没有理由有成员函数。你可以只在源代码中做,而不是resetmy_variable = {};my_variable.reset();
1赞 Konrad Rudolph 6/22/2023
请注意,如果您发现自己经常创建这样的“重置”函数,那就是代码异味。使用适当的类设计,应该很少(如果有的话)需要重置类实例(除了实现移动构造,因为......C++缺乏破坏性动作)。

答:

5赞 dwto 6/22/2023 #1

是的,没关系。

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

operator= 需要对另一个 Foo 对象的引用:

operator=(const Foo&)

operator=(Foo&&)

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

在你的例子中,由于你给出了一个空列表,你只需要求隐式转换为 Foo{},这意味着你希望有这个代码:

*this = Foo{};

这意味着为我的对象分配默认构造的 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();
    }
};
  • 在 C++20 中,使用 consteval 而不是 consexpr,尽管它不应该对生成的机器代码产生影响。

评论

0赞 cadam 6/23/2023
谢谢你的信息。但我不明白最后一个例子是如何可扩展的。如果有多个成员怎么办?
0赞 dwto 6/23/2023
添加了多成员重置选项
0赞 HolyBlackCat 6/23/2023
您的建议要求成员类型是默认可构造的,并在类构造函数中导致冗余成员 default-constructor 调用。
1赞 cadam 6/23/2023
感谢您考虑反馈!但是,您的最后一个示例现在重现了我的原始问题,因为您在构造函数和重置函数中复制了成员赋值。这就是我在上一条评论中所说的“不可扩展”的意思:)此外,如果复位不是复位,则不能同时将m1_reset标记为复位和复位。
1赞 cadam 6/23/2023
此外,'constexpr Foo() {}' 应该被标记为 '=default' 以使其变得微不足道
1赞 HolyBlackCat 6/23/2023 #2

是的,这是完全合法的。

在某些情况下可能不起作用(例如,如果默认构造函数是 ,也许在其他一些情况下),但将始终有效。= {}explicit= Foo{}

鉴于它涉及创建和复制对象,以这种方式(不进行优化)会不会效率较低?

也许它的效率略低,但不要担心过早的优化,除非你绝对确定这将是 yoru 代码中对性能至关重要的部分。

您认为这是避免重复的好做法吗?还是有另一种更合意的处理方式?

是的,避免重复是一件好事。我不知道有更好的方法。