在编译时检测 C++ 代码中的重复工作
Detecting duplicate work at compile time within C++ code
让我们考虑以下代码:
struct Foo {
Foo(Bar b1, Bar b2) : b1(b1), b2(b2) {}
void work() {
b1.work();
b2.work();
//something
}
Bar& b1;
Bar& b2;
};
struct Bar {
void work() { /* something */ }
};
int main() {
Bar a, b;
a.work();
//something...
Foo c(a,b);
c.work();
//something...
}
我写的(或打算写的)a.work() 将执行两次。但是假设,我,正如程序员所知,执行两次是浪费执行时间,假设这是一个复杂得多的软件的一部分,手动跟踪什么工作是太麻烦了,未完成。
显然我可以在 Bar 中存储一些布尔标志并每次检查工作是否已经完成,但我想知道是否有某种方法可以在编译时捕获它。因为在编译的时候已经很清楚工作已经完成了。
不,不是真的。
编译器能够一些静态分析,如果您可以要求它诊断这种情况,它可能能够在一些简单的情况下这样做。但是一旦你有一个不平凡的流程(例如运行时 if
条件),它就会很快超出 window。这可能是没有人为编译器创建这种可编程功能的部分原因:复杂性高,实用性可以忽略不计。
也许可以编写一些第三方静态分析器(或创建一个!)来诊断您的简单案例,但同样需要大量工作来处理您已经可以用您的工具发现的最微不足道的案例眼睛.
相反,您可以使 work()
在 Bar
构造函数中发生。那么不可能在同一个对象上做两次工作。但是,在构造函数中执行大量工作通常不受欢迎。
我确实会在 Bar
内保留状态标志,并在后续 work()
中保留 return false
,相应地保持该标志的值。作为奖励,在 returning false
之前在函数中添加一个断言,以便在测试期间发现违规行为。
状态标志不必是布尔值;它可以是 enum
。对象内部的稳健状态机非常有用。
也就是说,我建议重新审视您当前的方法,即您将对事物的引用传递给对它们起作用的其他事物;这不是一个易于遵循的设计,这只是您设计的一个简单示例!您可能希望考虑传递一些一次性代理类型。
实际上,在编译时,代码是否被执行并不明显。假设您有一个 if 语句,并在其中调用 a.work()
。编译器如何知道 a.work()
是否在那个时候被执行了?正如您所说,不要认为 if
语句非常简单(假设它正在寻找一些外部信号并根据该信号执行代码)。避免这种行为的最好方法是保留一个布尔值。
另一种方法。在 Bar
对象中有一个函数指针,在 work()
函数中调用 pointer
。在构造函数中,定义指针为实际工作函数。在函数结束时,将指针重新分配为一个空函数。
在这种情况下,第一次执行即可。但稍后的执行将不执行任何操作(也不检查 boolean
标志)
struct Bar {
typedef void (*Bar::fptr_t)();
Bar() : fptr(actual_work) {}
void actual_work() {
/*something*/;
fptr = &Bar::empty_work;
}
void empty_work() {}
void work() {fptr();}
fptr_t fptr;
};
类似上面的内容。
让我们考虑以下代码:
struct Foo {
Foo(Bar b1, Bar b2) : b1(b1), b2(b2) {}
void work() {
b1.work();
b2.work();
//something
}
Bar& b1;
Bar& b2;
};
struct Bar {
void work() { /* something */ }
};
int main() {
Bar a, b;
a.work();
//something...
Foo c(a,b);
c.work();
//something...
}
我写的(或打算写的)a.work() 将执行两次。但是假设,我,正如程序员所知,执行两次是浪费执行时间,假设这是一个复杂得多的软件的一部分,手动跟踪什么工作是太麻烦了,未完成。
显然我可以在 Bar 中存储一些布尔标志并每次检查工作是否已经完成,但我想知道是否有某种方法可以在编译时捕获它。因为在编译的时候已经很清楚工作已经完成了。
不,不是真的。
编译器能够一些静态分析,如果您可以要求它诊断这种情况,它可能能够在一些简单的情况下这样做。但是一旦你有一个不平凡的流程(例如运行时 if
条件),它就会很快超出 window。这可能是没有人为编译器创建这种可编程功能的部分原因:复杂性高,实用性可以忽略不计。
也许可以编写一些第三方静态分析器(或创建一个!)来诊断您的简单案例,但同样需要大量工作来处理您已经可以用您的工具发现的最微不足道的案例眼睛.
相反,您可以使 work()
在 Bar
构造函数中发生。那么不可能在同一个对象上做两次工作。但是,在构造函数中执行大量工作通常不受欢迎。
我确实会在 Bar
内保留状态标志,并在后续 work()
中保留 return false
,相应地保持该标志的值。作为奖励,在 returning false
之前在函数中添加一个断言,以便在测试期间发现违规行为。
状态标志不必是布尔值;它可以是 enum
。对象内部的稳健状态机非常有用。
也就是说,我建议重新审视您当前的方法,即您将对事物的引用传递给对它们起作用的其他事物;这不是一个易于遵循的设计,这只是您设计的一个简单示例!您可能希望考虑传递一些一次性代理类型。
实际上,在编译时,代码是否被执行并不明显。假设您有一个 if 语句,并在其中调用 a.work()
。编译器如何知道 a.work()
是否在那个时候被执行了?正如您所说,不要认为 if
语句非常简单(假设它正在寻找一些外部信号并根据该信号执行代码)。避免这种行为的最好方法是保留一个布尔值。
另一种方法。在 Bar
对象中有一个函数指针,在 work()
函数中调用 pointer
。在构造函数中,定义指针为实际工作函数。在函数结束时,将指针重新分配为一个空函数。
在这种情况下,第一次执行即可。但稍后的执行将不执行任何操作(也不检查 boolean
标志)
struct Bar {
typedef void (*Bar::fptr_t)();
Bar() : fptr(actual_work) {}
void actual_work() {
/*something*/;
fptr = &Bar::empty_work;
}
void empty_work() {}
void work() {fptr();}
fptr_t fptr;
};
类似上面的内容。