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

时间:2019-04-11 16:28:45

标签: 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);
}

它会产生以下输出,看起来就像我想要的一样:

  

set_a
  更新

     

set_a
  set_b
  更新

     

set_a
  set_b
  set_c
  更新

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

我之所以问是因为出于某种原因我对此感到不好。也许我的不满意来自于Visual Studio的警告:

  

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

但是也许/希望我的不良心情是没有道理的,而警告可以忽略不计?

让我最困扰的是:在任何情况下都不会调用update_internal_state方法(可能是由于滥用我的类或通过某种编译器优化等)? >

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

3 个答案:

答案 0 :(得分:3)

我认为您的解决方案是合法的,但是它的缺点是它对代码的用户隐藏了,更新昂贵,因此更可能编写:

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

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

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

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的唯一方法-用户将更倾向于在一次交易中调用多个设置器。

答案 1 :(得分:2)

我在这里看到的危险是,如果您的类具有不返回代理(或任何公共成员)的 any 方法。如果使用代理服务器的operator->(产生complicated),则禁用更新调用,但这仅在operator->总是使用 产生收益时才是安全的另一个代理对象,它将接管更新任务。对于以后修改课程的任何人来说,这似乎都是一个巨大的陷阱。

我认为,如果complicated跟踪在其上创建的活动proxy对象的数量,以便最后一个要销毁的proxy执行更新调用,那将是更安全的。

答案 2 :(得分:0)

遵循Dmitry Gordon的观点,即人们选择“错误”的方法,但是事情可能会更简单一些(尤其是从用户的角度来看):

class Demo
{
    int m_x
    int m_y; // cached value, result of complex calculation
    bool m_isDirtyY;
public:
    int x() { return m_x; }
    void x(int value) { m_x = value; m_isDirtyY = true; }
    int y()
    {
        if(m_isDirtyY)
        {
            // complex calculations...
            m_y = result;
            m_isDirtyY = false;
        }
        return m_y;
    }
};

这样,您将只根据需要执行计算,而不会像代理对象或显式事务那样进行其他扩展。

根据您的需要,您可能将此模式封装在一个单独的(模板?)类中,也许会接收一个更新程序对象(lambda?),或者使用纯虚函数update来重复更少的代码。

侧面说明:设置值可能会使一个以上的缓存值无效–没问题,然后将多个脏标志设置为true,然后...