【问题标题】:What is a good pattern for array template compatibility?数组模板兼容性的好模式是什么?
【发布时间】:2022-07-07 01:14:48
【问题描述】:

我想创建具有可变长度元素数组的对象,并让它们在基类/派生类意义上兼容。在 C 中,可以在 struct 的末尾放置一个不确定的数组,然后只需 malloc 对象以包含完整的数组:

struct foo {
    int n;
    double x[];
} ;

struct foo *foo1 = (foo *)malloc( sizeof( foo ) + sizeof( double[4] ) );
struct foo *foo2 = (foo *)malloc( sizeof( foo ) + sizeof( double[100] ) );

在 c++ 中,您似乎可以这样做:

template <unsigned I>
class foo {
public:
    int n;
    double x[I];
} ;

但是:

auto foo1 = new foo<4>( );
auto foo2 = new foo<100>( );

if (foo1 == foo2) cerr << "incompatible pointers";

你可以用一个通用的基类来做到这一点,但这有必要吗?我只想使用foo1foo2,每个对象都知道其数组的长度。


我的应用程序适用于运行 FreeRTOS 的 ESP32 微控制器。它具有有限的非虚拟 RAM 和稍微复杂的分配系统,因为各种内存块的功能不同(有些速度较慢,有些不能包含可执行代码,有些不能通过 DMA 访问等)所以分配一个对象的多个块(例如,在末尾使用std::vector 来表示double 的数组)变得复杂。

我在对象构造时知道double 数组的长度,但我希望标头和数组位于单个分配的内存块中(因此它可以具有我以后需要的特性)。

C 风格的做法会很好,但如果有 C++ 特性,比如对数组进行迭代(对于各种对象,每个对象都有不同数量的 doubles),那就太好了。另外,本机 C++ 解决方案将允许我在 x[] 数组中拥有对象,而不是在原始分配的内存中随意放置 new。所以,例如:

auto a[] = { new foo<5>( ), new foo<10>( ), new foo<15>( ) };

for (auto i : a)
    for (auto j : i.x)
        cout << log10( j );    // prints 40 logs of doubles

(我预计会有 C++ 语法错误,但希望它能传达这个想法。如果我可以将所有 foos 放入一个公共容器中,我可以弄清楚它的语法。)

【问题讨论】:

  • 这就是std::vector 的用途。
  • 为什么不std::vector?我想你是有原因的。
  • 如果你想比较两种不同类型的两个指针(很奇怪),你可以将它们转换为void*。如果你想比较这两个对象(去掉那个无用的new),你必须告诉编译器你打算如何比较两个不同大小的数组,为你的类族提供一个模板化的operator==
  • foo&lt;4&gt;foo&lt;100&gt; 是两个东西。您需要了解模板是什么。请改用std::vector&gt;
  • 您的意思是“打印 40 个双打日志”吗?

标签: c++ arrays templates flexible-array-member


【解决方案1】:

作为一名低级 C++ 开发人员,我完全理解您的需求,遗憾的是,无论有没有模板,标准 C++ 中的灵活数组成员都无法替代。您必须通过编译器扩展继续使用灵活的数组成员。

它们不包含在语言中,因为在当前形式下,它们本质上是一种 hack。它们在继承或组合方面做得不好。

模板的问题在于,灵活数组版本有一个通用类型的所有大小的数组。这意味着您可以将它们放在数组中,让非模板函数将它们作为参数等:

foo* make_foo(int n);

foo* foos[] = { make_foo(1); make_foo(2); make_foo(3); }; // ok

void take_foo(foo*);

在模板版本中,foo&lt;1&gt;foo&lt;2&gt; 类型完全不相关,因此您不能将它们放入数组或使用非模板函数来获取它们:

template <int N>
foo<N>* make_foo();

auto foos[] = { make_foo<1>(), make_foo<2>(), make_foo<3>() }; // ill-formed

template <int N>
void take_foo(foo<N>*);

std::array 在本次讨论中无济于事,从 one 继承仍然存在类型不相关的问题。

但是,由于这仍然是 C++(尽管不是标准的),您至少可以有 一些 额外的细节:

template <class T>
struct flex_array {
  int n;
  T data[];

  T* begin() { return &data[0]; }
  T* end() { return begin() + n; }
};


void iterate(flex_array<double>& f) {
    for (double j : f) {
        cout << log10(j); // print however many doubles are in f
    }
}

【讨论】:

  • 嗯,这不是好消息,但至少它节省了我一些时间来处理模板!谢谢!
  • 我又在这个问题上胡闹了一些,而且大部分都按照我想要的方式工作。这是一个带有代码(和一些调试框架)的 JDoodle:jdoodle.com/embed/v0/4zhqflex_array&lt;T&gt; 的工作方式与 std::array&lt;T,N&gt; 类似,但数组长度不是类型签名的一部分,因此您可以将不同长度的 Ts 数组传递给函数等。如果我能找到完美的转发,我希望能够在适当的位置构造数组,那就太好了!
  • 我还没有完全阅读,但是你为什么会有这个? static const T *tp = 0; static const size_t ts = (const char *)(tp + 1) - (const char *)(tp); 你想得到sizeof(T) 吗?
  • 您还可以将整个内容发布到代码审查堆栈交换中以获得一些全面的反馈。
  • 填充字节在sizeof 中计算,因此您将获得相同的大小,除非没有未定义的行为。 nullptr 上的算术是 UB IIRC。由于 0 大小的数组是非标准的,因此您的整体 flex_array 如何对齐将在此处定义实现,但我希望它就像您有一个非零大小的数组一样,所以没关系。
猜你喜欢
  • 2011-08-12
  • 2016-11-09
  • 2018-01-09
  • 2011-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多