使用代理对象延迟更新与“避免使用自定义构造和销毁的未命名对象”

j00*_*0hi 3 c++ c++11 c++14 c++17

我有一个类complicated,其中包含修改一些内部状态的各种设置器。内部状态修改可能很昂贵,所以我不想经常这样做。特别是,如果几个 setter 被立即连续调用,我想在这些 setter 调用的最后一次之后只执行一次内部状态的昂贵更新。

我已经通过代理解决了(或“解决了”?)这个要求。以下将是一个最小的工作代码示例:

#include <iostream>

class complicated
{
public:
    class proxy
    {
    public:
        proxy(complicated& tbu) : to_be_updated(&tbu) {
        }

        ~proxy() {
            if (nullptr != to_be_updated) {
                to_be_updated->update_internal_state();
            }
        }

        // If the user uses this operator, disable update-call in the destructor!
        complicated* operator->() {
            auto* ret = to_be_updated; 
            to_be_updated = nullptr;
            return ret;
        }
    private:
        complicated* to_be_updated;
    };

public:
    proxy set_a(int value) {
        std::cout << "set_a" << std::endl;
        a = value;
        return proxy(*this);
    }

    proxy set_b(int value) {
        std::cout << "set_b" << std::endl;
        b = value;
        return proxy(*this);
    }

    proxy set_c(int value) {
        std::cout << "set_c" << std::endl;
        c = value;
        return proxy(*this);
    }

    void update_internal_state() {
        std::cout << "update" << std::endl;
        expensive_to_compute_internal_state = a + b + c;
    }

private:
    int a;
    int b;
    int c;
    int expensive_to_compute_internal_state;
};

int main()
{
    complicated x;
    x.set_a(1);
    std::cout << std::endl;
    x.set_a(1)->set_b(2);
    std::cout << std::endl;
    x.set_a(1)->set_b(2)->set_c(3);
}
Run Code Online (Sandbox Code Playgroud)

它产生以下输出,看起来正是我想要的:

set_a
更新

SET_A
set_b
更新

SET_A
set_b
set_c
更新

我的问题是:我的方法是否合法/最佳实践?
是否可以依赖proxy将在分号处销毁的临时对象(即返回的对象)?

我之所以这么问是因为出于某种原因我对此有不好的感觉。也许我的不好的感觉只是来自 Visual Studio 的警告,它说:

警告 C26444 避免使用自定义构造和销毁的未命名对象 (es.84)。

但也许/希望我的坏情绪是不合理的,可以忽略该警告?

最让我烦恼的是:是否有任何情况下update_internal_state不会调用该方法(可能是由于滥用我的类或某些编译器优化或其他原因)?

最后:有没有更好的方法来实现我试图用现代 C++ 实现的目标?

Dmi*_*don 5

我认为你的解决方案是合法的,但它有一个缺点,它对代码的用户隐藏,更新很昂贵,所以人们更有可能写:

x.set_a(1);
x.set_b(2);
Run Code Online (Sandbox Code Playgroud)

x.set_a(1)->set_b(2);
Run Code Online (Sandbox Code Playgroud)

我建议将 setter 设为私有并添加一个朋友事务类,以便修改对象看起来像:

complicated x;
{
    transaction t(x);
    t.set_a(1);
    t.set_b(2);
    // implicit commit may be also done in destructor
    t.commit();
}
Run Code Online (Sandbox Code Playgroud)

如果transaction将是唯一的修改方式complicated- 用户将更倾向于在一个事务中调用多个 setter。