首页 > 解决方案 > 使用代理对象延迟更新与“使用自定义构造和销毁避免未命名对象”

问题描述

我有一个类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);
}

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

set_a
更新

set_a
set_b
更新

set_a
set_b
set_c
更新

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

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

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

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

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

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

标签: c++c++11c++14c++17

解决方案


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

x.set_a(1);
x.set_b(2);

x.set_a(1)->set_b(2);

我建议将 setter 设为私有并添加一个朋友事务类,以便修改对象如下所示:

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

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


推荐阅读