debugging 在C++中,使用宏实现带有自己的循环计数器的for语句

yx2lnoni  于 12个月前  发布在  其他
关注(0)|答案(4)|浏览(190)

我的要求是在程序的整个运行时间内,计算每个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循环生成一个静态计数器for_counter_N,其中N代表循环所在的行号,我可以在调试环境中看到这个循环发生的总次数,但是由于它们是局部静态变量,我不能在程序结束时从main函数中全部打印出来。
有什么方法可以实现我的目的吗?它不一定是宏,因为我使用的是C++,如果我可以使用模板等来绕过它就好了。或者如果有一个调试工具可以做到这一点,也可以工作。
编辑:
很抱歉我没有指定,这并不是说我没有考虑过使用map或unordered_map,但是由于在我的程序中for循环的性能要求相当苛刻(因为我正在做一些加密算法优化的工作),使用这些数据结构会对性能产生一些影响。

tcbh2hod

tcbh2hod1#

只要有可能,我就尽量远离宏。在这种情况下,我会创建一个行为类似于int的类型,但会跟踪它的递增频率。对于这个演示(https://onlinegdb.com/zTJqjvNvE),报告是在析构函数中(所以当计数器超出范围时)。
我的思路是,你不需要知道什么样的循环,只是需要知道某个东西递增的频率(这种方法也适用于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
}

字符串

mnowg1ta

mnowg1ta2#

使用全局map计数器,__FILE____LINE__的组合作为键(例如std::pair<std::string_view, int>键)。

8zzbczxx

8zzbczxx3#

模板功能似乎更好:

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);
}


并将Map更改为std::string作为键。
Demo

eqfvzcg8

eqfvzcg84#

我想我会像其他人建议的那样使用全局Map,* 但是 * 通过创建一个 Package 类来初始化对ctor中counter对象的引用,从而避免了大量的开销,所以从那时起,它就像您的代码一样,只是做了几个正常的增量。
如果您真的愿意,您甚至可以通过创建一个局部变量、在每次迭代中更新它,然后将其添加到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();
}

字符串
为了使开销最小,我编写了这个函数,让它的后增量运算符只返回int形式的前一个值,而它通常应该返回*this的一个副本(作为临时的LoopCounterImpl对象)。但是在循环中,通常只执行foo++,以避免增加变量的副作用,而不关心返回值,你能用临时循环计数器做的就是把它的当前值作为一个int来检索,所以我只返回了一个int来减少开销。
目前,它使用一个LoopCounter宏来自动检索__LINE__的正确值。这反过来又迫使你使用像auto foo = LoopCounter(initialValue)这样的东西,这在我看来并不可怕,但我想有些人可能会不喜欢。如果你不介意像LoopCounter x{0, __LINE__}这样的东西,你可以省去它。
显然,您还可以添加operator+=-=--等。我想,从现有的内容来看,大部分内容都很明显。
如果要将其放入标题中,则需要使用LoopCounter宏来收集__FILE__沿着__LINE__,并将两者都用作Map中的键。

相关问题