我有一个类complicated
,其中包含修改某些内部状态的各种setter。内部状态修改可能很昂贵,所以我不想经常这样做。特别是,如果立即连续调用几个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 ++实现的目标?
我认为你的解决方案是合法的,但它有一个缺点,它隐藏在代码的用户,更新是昂贵的,所以更可能写:
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
的唯一方法 - 用户将更倾向于在一次交易中调用几个setter。
我在这里看到的危险是你的班级有任何方法不返回代理(或任何公共成员)。如果使用代理的operator->
(产生complicated
),则禁用更新调用,但只有在使用operator->
总是产生另一个将接管更新任务的代理对象时,这才是安全的。对于后来修改课程的人来说,这似乎是一个巨大的陷阱。
我认为如果complicated
跟踪在其上创建的活着的proxy
对象的数量将更安全,以便要销毁的最后一个proxy
执行更新调用。
根据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然后......