问题
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<<
onLOG
which may now changeLOG.ulog.active
based on an intervening modification ofprintLog
or byconst_cast
ing the result - Calling the defaulted copy/move assignment operators
- (since this is a standard-layout class)
reinterpret_cast
LOG
to a reference to itsulog
member - (since the classes are trivially copyable)
memcpy
a different state intoLOG
- placement-new a new instance of
Log
intoLOG
, which will make previous references reference the new object automatically, becauseLog
satisfies the conditions for that - etc.
来源:https://stackoverflow.com/questions/59291618/is-this-a-missed-optimization-opportunity-or-not