对数组中特定位置的调试器友好零成本命名引用

Debugger-friendly zero-cost named references to specific locations in an array

本文关键字:零成 引用 数组 定位 位置 调试器      更新时间:2023-10-16
struct A
{
    int a   = 1;
    short b = 2;
    char c  = 3;
}
struct B
{
    using arr_type = array<A,3>;
    char asd = 0;
    A a1;  
    A a2;
    A a3;
    // is this safe to use to loop trough all 3 elements?
    arr_type* p1 = reinterpret_cast<arr_type*>(&a1);
    // or maybe this one?
    A* p2 = &a1;
};

我可以安全地使用p1p2a1...a3循环吗?

B b;
for (int i = 0; i < 3; i++)
{
     cout << p1[i];
     cout << p2[i];
}

它不是一个简单的数组的原因是我希望每个"项目"都有一个正确的名称。

我可以改用联合方法,但C++禁止匿名结构(尽管这对我来说不是问题,因为 MSVC 支持这一点,GCC 似乎也支持它(;

union E
{
    A arr[3];
    struct {
        A a1;
        A a2;
        A a3;
    };
};

以下内容显然是安全的,但每个引用都有 4 个字节的开销。我不喜欢。(加上初始化引用的成本。

struct B
{
    char asd;
    A arr[3];  
    A& a1 = arr[0];
    A& a2 = arr[1];
    A& a3 = arr[2];
};

这个没有开销,但对于我非常具体的情况来说,这还不够好。

struct B
{
    char asd;
    A arr[3];  
    A& a1() { return arr[0] };
    A& a2() { return arr[1] };
    A& a3() { return arr[2] };
};

我将经常使用这些a1, a2, a3名称,如果它们是Visual Studio中的函数调用,则很难调试它们。同样,我将大量使用这些字段,因此我希望能够轻松检查它们的值。

struct B
{
     using arr_type = array<A,3>;
     char asd = 0;
     A a1;  
     A a2;
     A a3;
     // is this safe to use to loop trough all 3 elements?
     arr_type* p1 = reinterpret_cast<arr_type*>(&a1);
 };

结构需要根据它们的类型自然对齐,数组也是如此,但我不知道有任何规则说它们必须是相同的对齐点。

如果有这样的规则,像这样的成员的结构布局边界和数组边界将是相同的 - 它仅适用于标准布局结构:

https://stackoverflow.com/a/7189821/211160

如果您执行以下操作,则所有赌注都将关闭:

private:
    A a1;
    A a2;
public:
    A a3;

我想如果它包含任何关闭标准布局开关的东西,所有的赌注都会失败。 由于一开始就值得怀疑,我想说当时甚至不要这样做。

(我也想知道数组与结构#pragma pack()会带来什么样的差异......并不是说 #pragmas 在标准中,我只是想知道。

union E
{
    A arr[3];
    struct {
        A a1;
        A a2;
        A a3;
    };
};

不,arr[N]aN不会等同。 关于如何在联合中使用初始序列以兼容C++读取有一些微妙的细节......但这仅在具有兼容序列的结构之间。 它没有说明结构和数组:

键入 C 中的结构,并通过联合C++

我将经常使用这些 a1、a2、a3 名称,如果它们是 Visual Studio 中的函数调用,则很难调试它们。同样,我将大量使用这些字段,因此我希望能够轻松检查它们的值。

"以下内容显然是安全的,但每个引用都有 4 个字节的开销">

在实践中,您似乎是对的,今天的 GCC 并没有对其进行优化(根据您的链接(:

https://godbolt.org/g/6jAtD5

http://ideone.com/zZqfor

这令人失望,它们可以被优化出来,因为标准中没有任何内容说它们必须占用空间。 它们在内部指向结构,并且在结构的生命周期内不会更改。 :-/

您对将被优化的函数访问的抱怨是它对调试器不够友好。 为什么不两者兼而有之呢?

struct B
{
    char asd;
    A arr[3];
    A& a1() { return arr[0] }
    const A& a1() const { return arr[0]; }
    A& a2() { return arr[1] };
    const A& a2() const { return arr[1]; }
    A& a3() { return arr[2] };
    const A& a3() const { return arr[2]; }
#if !defined(NDEBUG)
    A& a1_debug = arr[0];
    A& a2_debug = arr[1];
    A& a3_debug = arr[2];
#endif
};
<小时 />

如果投影数据结构的调试器友好性功能对您很重要......学习如何为您的环境编写自定义调试器帮助程序可能会很好地利用时间,例如:

http://doc.qt.io/qtcreator/creator-debugging-helpers.html

我想这是否值得取决于你多久有这种担忧。

不需要这么肮脏。

std::tuple与 lambda 相结合,为您提供所需的所有功能。此外,它是完全合法、最佳和正确的。

如果我们定义一个成员函数,该函数返回对结构中所有 As 的引用元组:

  auto all_a() const {
    return std::tie(a1, a2, a3);
  }

。然后创建一个小管道,以提供一种在元组上行走的方法(见下文(......

。我们可以编写这样的代码:

  B b;
  for_each(b.all_a(), 
           [](const A& a) { std::cout << a << std::endl; });

完整示例(虽然我没有实现运算符<<,但您可以自己执行此操作(。

#include<iostream>
#include<array>
#include<tuple>
#include<utility>
using namespace std;
struct A
{
    int a   = 1;
    short b = 2;
    char c  = 3;
};
std::ostream& operator<<(std::ostream& os, const A& a);
struct B
{
    char asd = 0;
    A a1;  
    A a2;
    A a3;
  auto all_a() const {
    return std::tie(a1, a2, a3);
  }
};

template<class Tuple, size_t...Is, class F>
  void for_each_impl(const Tuple& t, std::index_sequence<Is...>, F&& f)
{
  using expand = int[];
  void(expand { 0, (void(f(std::get<Is>(t))),0)... });
}
template<class...Ts, class F>
void for_each(const std::tuple<Ts...> ts, F&& f)
{
  using expand = int[];
  for_each_impl(ts, 
                std::make_index_sequence<sizeof...(Ts)>(), 
                std::forward<F>(f));
}
int main()
{
  B b;
  for_each(b.all_a(), 
           [](const A& a) { std::cout << a << std::endl; });
}