问题
I posted this answer. Code:
#include <atomic>
#include <utility>
void printImpl(...);
std::atomic<bool> printLog = false;
class Log {
public:
template <typename T>
const auto& operator<<(T&& t) {
if (printLog) {
ulog.active = true;
return ulog << std::forward<T>(t);
} else {
ulog.active = false;
return ulog;
}
}
private:
struct unchecked_log {
template <typename T>
const auto& operator<<(T&& t) const {
if (active) {
printImpl(std::forward<T>(t));
}
return *this;
}
bool active{false};
};
unchecked_log ulog{};
};
// Instead of the macro. Doesn't break backward compatibility
Log LOG;
void test(bool) { LOG << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10; }
In essence, the code either ignores or logs all the data. The idea was to record the atomic<bool> in a regular bool which can be optimized out more easily. I thought most compilers could easily optimize out the if (active) part since there is no way it can change between calls. Turns out though, most compilers do inline the function call to unchecked_log::operator<< but do not optimize out the branching. Is there something preventing this optimization? Would it be illegal.
回答1:
LOG is a global variable with external linkage. Therefore printImpl's definition in another translation unit can reach it and can potentially modify LOG.ulog.active between calls.
Make LOG a local variable in test and the repeated checks will be consolidated into one at the entry of test or leave LOG where it is and make it static, so a different compilation unit containing printImpl's definition cannot reach this translation unit's instance.
As mentioned in a comment below, alternatively let operator<< return by copy, so that the instance it returns (now a temporary) is unreachable for printImpl.
Note that the accessibility (private, etc.) of ulog and ulog.active does not matter. As soon as printImpl is able to obtain a pointer or reference to the instance of relevance, private does not protect from modification no matter what. Here are few examples of how that is possible (non-exhaustive):
- Calling
operator<<onLOGwhich may now changeLOG.ulog.activebased on an intervening modification ofprintLogor byconst_casting the result - Calling the defaulted copy/move assignment operators
- (since this is a standard-layout class)
reinterpret_castLOGto a reference to itsulogmember - (since the classes are trivially copyable)
memcpya different state intoLOG - placement-new a new instance of
LogintoLOG, which will make previous references reference the new object automatically, becauseLogsatisfies the conditions for that - etc.
来源:https://stackoverflow.com/questions/59291618/is-this-a-missed-optimization-opportunity-or-not