这是一个错过的优化机会吗?
Is this a missed optimization opportunity or not
我发布了 this 答案。代码:
#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; }
本质上,代码忽略或记录所有数据。想法是在常规 bool
中记录 atomic<bool>
,这样可以更容易地优化掉。我认为大多数编译器可以轻松优化 if (active)
部分,因为它无法在调用之间更改。 Turns out 但是,大多数编译器会将函数调用内联到 unchecked_log::operator<<
但不会优化分支。有什么东西阻止了这种优化吗?会不会违法。
LOG
是一个有外部链接的全局变量。因此 printImpl
在另一个翻译单元中的定义可以到达它并且可以在调用之间修改 LOG.ulog.active
。
将LOG
设为test
中的局部变量,重复的检查将在test
的入口处合并为一个,或者将LOG
留在原处并make它 static
,因此包含 printImpl
定义的不同编译单元无法访问此翻译单元的实例。
正如下面评论中提到的,或者让 operator<<
return 复制,这样 return 实例(现在是临时的)对于 [=11= 是不可访问的].
请注意,ulog
和 ulog.active
的可访问性(private
等)无关紧要。一旦 printImpl
能够获得相关实例的指针或引用,无论如何 private
都无法防止修改。以下是如何实现的几个示例(非详尽无遗):
- 在
LOG
上调用 operator<<
现在可能会根据 printLog
的干预修改或通过 const_cast
结果 [=55] 更改 LOG.ulog.active
=]
- 调用默认的 copy/move 赋值运算符
- (因为这是标准布局 class)
reinterpret_cast
LOG
对其 ulog
成员的引用
- (因为 classes 是可以简单复制的)
memcpy
一个不同的状态进入 LOG
- placement-new
Log
的新实例到 LOG
中,这将使之前的引用自动引用新对象,因为 Log
满足 的条件]
- 等等
我发布了 this 答案。代码:
#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; }
本质上,代码忽略或记录所有数据。想法是在常规 bool
中记录 atomic<bool>
,这样可以更容易地优化掉。我认为大多数编译器可以轻松优化 if (active)
部分,因为它无法在调用之间更改。 Turns out 但是,大多数编译器会将函数调用内联到 unchecked_log::operator<<
但不会优化分支。有什么东西阻止了这种优化吗?会不会违法。
LOG
是一个有外部链接的全局变量。因此 printImpl
在另一个翻译单元中的定义可以到达它并且可以在调用之间修改 LOG.ulog.active
。
将LOG
设为test
中的局部变量,重复的检查将在test
的入口处合并为一个,或者将LOG
留在原处并make它 static
,因此包含 printImpl
定义的不同编译单元无法访问此翻译单元的实例。
正如下面评论中提到的,或者让 operator<<
return 复制,这样 return 实例(现在是临时的)对于 [=11= 是不可访问的].
请注意,ulog
和 ulog.active
的可访问性(private
等)无关紧要。一旦 printImpl
能够获得相关实例的指针或引用,无论如何 private
都无法防止修改。以下是如何实现的几个示例(非详尽无遗):
- 在
LOG
上调用operator<<
现在可能会根据printLog
的干预修改或通过const_cast
结果 [=55] 更改LOG.ulog.active
=] - 调用默认的 copy/move 赋值运算符
- (因为这是标准布局 class)
reinterpret_cast
LOG
对其ulog
成员的引用 - (因为 classes 是可以简单复制的)
memcpy
一个不同的状态进入LOG
- placement-new
Log
的新实例到LOG
中,这将使之前的引用自动引用新对象,因为Log
满足 的条件]
- 等等