我有一个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 ++实现的目标?
答案 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,然后...