提问人:Jemtaly 提问时间:10/30/2023 最后编辑:Jemtaly 更新时间:10/31/2023 访问量:194
在 C++ 中使用宏实现具有自己的循环计数器的 for 语句
Use macros to implement a for statement with its own loop counter in C++
问:
我的要求是计算程序循环中每个for语句在程序的整个运行时的总次数,例如:
int for_counter_1 = 0;
int for_counter_2 = 0;
void f() {
for (int i = 0; i < 10; i++, for_counter_2++) {
// ...
}
}
int main() {
for (int i = 0; i < 10; i++, for_counter_1++) {
f();
}
printf("%d\n", for_counter_1); // should output: 10
printf("%d\n", for_counter_2); // should output: 100
}
由于我的程序中有非常多的 for 循环(一些加密算法),并且我将继续扩展它,因此我考虑使用宏来实现具有自己的循环计数器的 for 语句,如下所示:
#define CONCAT_INNER(a, b) a ## b
#define CONCAT(a, b) CONCAT_INNER(a, b)
#define FOR_COUNTER() CONCAT(for_counter_, __LINE__)
#define for_with_counter(...) \
static int FOR_COUNTER() = 0; \
for (__VA_ARGS__, ++FOR_COUNTER())
void f() {
for_with_counter(int i = 0; i < 10; ++i) {
// ...
}
}
int main() {
for_with_counter(int i = 0; i < 10; ++i) {
f();
}
}
它可以为每个 for 循环生成一个静态计数器,其中 N 表示循环所在的行号。我可以看到在调试环境中发生此循环的总次数。但是,由于它们是局部静态变量,因此我无法在程序结束时从主函数中将它们全部打印出来。for_counter_N
有什么方法可以实现我的目的吗?它不一定是宏,而且由于我使用的是 C++,如果我可以使用模板等来绕过它就好了。或者,如果有一个调试工具可以做到这一点,那也可以工作。
编辑:
对不起,我没有指定,这并不是说我没有考虑过使用 map 或 unordered_map,但是由于我的程序中 for 循环的性能要求非常苛刻(因为我正在做一些加密算法优化方面的工作),使用这些数据结构会产生一些性能影响。
答:
使用全局计数器,将 和 组合为键(例如键)。map
__FILE__
__LINE__
std::pair<std::string_view, int>
评论
模板功能似乎更好:
extern std::/*unoredered_*/map<std::string_view, std::size_t> loop_counters; // put definition in cpp file.
// F callable which return false to stop the loop
template <typename F>
void for_with_counter(std::string_view loop_name, int begin, int end, F f) {
auto& counter = loop_counters[loop_name];
for (int i = begin; i != end; ++i) {
++counter;
if constexpr (std::is_same_v<void, decltype(std::invoke(f, i))>) {
std::invoke(f, i);
} else {
if (!std::invoke(f, i)) {
return;
}
}
}
}
用法
for_with_counter("my_loop", 0, 10, [/*..*/](int i){ /* .. */ });
未明确提供名称的版本:
template <typename F>
void for_with_counter(int begin, int end, F f, std::source_location loc = std::source_location::current()) {
auto name = std::string(loc.function_name()) + " " + loc.file_name() + ":" + std::to_string(loc.line());
for_with_counter(name, begin, end, f);
}
并将地图更改为 AS 键。std::string
评论
continue
std::views::iota(st,ed)
std::ranges::find_if
std::ranges::for_each
ranges
只要有可能,我就会尽量完全远离宏。
在本例中,我将创建一个类型,其行为类似于 ,但会跟踪它递增的频率。对于此演示 (https://onlinegdb.com/zTJqjvNvE),报告位于析构函数中(因此,当计数器超出范围时)。int
我的想法是,你不需要知道循环的类型,只需要知道某件事的递增频率(这种方法也适用于while循环)
#include <string>
#include <iostream>
template<typename type_t>
class counter_t
{
public:
explicit counter_t(const std::string name) :
m_name{ name },
m_count{ 0ul }
{
}
auto& operator=(const type_t& value)
{
m_value = value;
return *this;
}
void operator++()
{
m_value++;
m_count++;
}
void operator++(int)
{
++m_value;
++m_count;
}
operator const type_t& () const
{
return m_value;
}
operator type_t& ()
{
return m_value;
}
~counter_t()
{
std::cout << "counter : `" << m_name << "` was incremented " << m_count << " times\n";
}
private:
std::string m_name;
type_t m_value;
std::size_t m_count;
};
int main()
{
{
counter_t<int> counter1{ "counter1" };
for (counter1 = 1; counter1 < 10; ++counter1)
{
if (counter1 > 5) break;
}
} // go out of scope and report
return 1; // can set breakpoint here
}
我想我会像其他人建议的那样使用全局映射,但通过创建一个包装类来初始化对其 ctor 中计数器对象的引用来避免它产生重大开销,所以从那时起,它只是做几个正常的增量,就像你的代码一样。
如果你真的愿意,你甚至可以通过创建一个局部变量,在每次迭代中更新它,然后将其添加到 dtor 中的全局变量来避免在每次迭代中使用引用的潜在开销(但对我来说,这似乎是相当多的工作,不太可能完成太多工作)。
对于从 0 开始每个循环并始终递增 1 的典型情况,只需在每次循环迭代中递增局部变量,然后将其添加到 dtor 中的全局计数器中,就可以进一步减少开销。
一方面,这确实可能会在进入循环时增加开销。另一方面,它可能会减少循环每次迭代的开销。
下面是概念验证级别代码的快速部分:
#include <map>
#include <iostream>
#include <source_location>
class LoopCounterImpl {
static std::map<int, int> counters;
int &globalCounter;
int counter;
public:
LoopCounterImpl(int initial_val, int loc)
: counter(initial_val), globalCounter(counters[loc])
{}
LoopCounterImpl &operator++() {
++counter;
return *this;
}
void operator++(int) {
++counter;
}
operator int() const { return counter; }
static void dump(std::ostream &os = std::cout) {
os << "\n\n" << __FILE__ << ":\n";
for (auto const &rec : counters) {
os << "line: " << rec.first << ", count: " << rec.second << "\n";
}
}
~LoopCounterImpl() { globalCounter += counter; }
};
std::map<int, int> LoopCounterImpl::counters;
#define LoopCounter(x) LoopCounterImpl(x, __LINE__)
// And a quick demo of using it:
int main() {
for (auto i = LoopCounter(0); i<10; ++i) {
std::cout << i << " ";
}
std::cout << "\n";
for (auto j = LoopCounter(0); j<20; j++) {
std::cout << j << " ";
}
LoopCounterImpl::dump();
}
为了将开销保持在最低限度,我编写了此操作,使其后增量运算符仅将前一个值作为 返回,其中它通常应返回 的副本(作为临时对象)。但是在循环中,通常只执行 a 来增加变量的副作用,而不关心返回值。使用临时 LoopCounter 所能做的就是检索其当前值,因此我刚刚返回了一个以最大程度地减少开销。int
*this
LoopCounterImpl
foo++
int
int
目前,它使用宏来自动检索 的正确值。反过来,这在某种程度上迫使你使用类似的东西,这对我来说似乎并不可怕,但我想有些人可能不喜欢。如果你不介意这样的事情,你可以放弃它。LoopCounter
__LINE__
auto foo = LoopCounter(initialValue)
LoopCounter x{0, __LINE__}
显然,您还可以添加 、 、 等内容。我猜从已经存在的东西来看,其中大部分是很明显的。operator+=
-=
--
如果你打算把它放到一个标题中,你需要宏来收集 和 ,并将两者用作映射中的键。LoopCounter
__FILE__
__LINE__
评论
std::unordered_map<unsigned, unsigned> counters; /* ... */ counters[1]++; /* ... */ for (auto const& counter : counters) { std::cout << "Counter " << counter.first << " have the value " << counter.second << '\n'; }
std::array
std::string