初始化在类类型 #define 中定义的非静态成员数组,不带默认 ctor

Initializing non-static member array of size defined in a #define of class type without default ctor

本文关键字:数组 静态成员 ctor 默认 类型 #define 定义 初始化      更新时间:2023-10-16

我有以下类:

//in some .h file
#define BARS_IN_FOO 5 //The only place where this number should be specified.
//All code should work when I change this
//in some .cpp file
struct Foo;
struct Bar { Foo & foo; Bar(Foo & foo) : foo{ foo } {} } //Cannot be default initialized
struct Foo {
std::array<Bar, BARS_IN_FOO> myBars;
Foo() :
myBars{ } //Error. Cannot default initialize Bars.
//I want to initialize all Bars with Bar{*this} but at this point I don't
//know how many Bar initializers I should put in the myBar initializer list
{}
}

那么我应该如何初始化Foo::myBars呢?Bars 的数量在编译时是已知的,但我只想在一个地方指定这个数字(最好在定义中,欢迎其他建议(。

Foo::mybars的用法 :

  • 它的大小在运行时永远不会改变,但我还没有决定数字是多少,但会在 [1..10](左右(范围内(数字在开发过程中可能会发生很大变化(。
  • 元素值永远不会改变 (始终是相同的非常量对象(。
  • FooBar之间的关系是组合的关系。Foo是由Bar组成的,它们的寿命是一样的。Bar没有Foo,反之亦然是没有意义的。
  • FooBar类是性能关键型代码的一部分,它们的大小将极大地影响程序的内存占用(80-90%的内存占用将Foo秒和Bar秒(。

编译器:MSVS2017版本15.3

编辑:如果这有帮助,将std::array<Bar, BARS_IN_FOO> myBars;更改为Bar myBars[BARS_IN_FOO];也可以。

重要编辑:BarFoo的所有 ctor 都是公开的。这是一个错误。我改变了这个。

如果你可以假设Bar是可移动/可复制的,你可以很容易地做到这一点:

template <std::size_t ... Is>
std::array<Bar, sizeof...(Is)> make_bar_array_impl(Foo& f, std::index_sequence<Is...>) {
return { (Is, f)... };
}
template <std::size_t N>
std::array<Bar, N> make_bar_array(Foo& f) {
return make_bar_array_impl(f, std::make_index_sequence<N>{});
}
Foo() : myBars(make_bar_array<BARS_IN_FOO>(*this)) {}

这可以很容易地重构为使用更通用的"重复"模板元编程实用程序。我怀疑任何模板元编程库都会有一些这样的实用程序,在这里我懒得分解它,因为它无论如何都是一行。但是,如果您经常遇到此类问题,则需要考虑(只需编写一个函数,该函数使用相同的表达式返回 N 条目初始值设定项列表(。

活生生的例子:http://coliru.stacked-crooked.com/a/aab004c0090cc144。抱歉,无法访问 MSVC 编译器。也编译了 14,因为我不需要任何 17 个功能。

编辑:即使Bar不可移动/复制,或者如果您认为事情不会得到优化,即使在这种情况下,实际上也可以修改此技术以工作。您可以改为生成array<std::reference_wrapper<Foo>, N。但这有点复杂,通常C++大多数东西应该是可移动的,并且通常构造函数调用不在关键路径中。如有必要,我仍然可以详细说明。

编辑2:另外,请不要为此使用 #define。constexpr static auto BARS_IN_FOO = 5;应该以完全相同的方式工作,除了它被正确命名(可能还有一些我忘记的其他宏观肮脏(。

解决方案是构建一个 char 数组(或 C++ 17 中的字节数组(,并从那里使用指针来拥有一个 Bar 数组。具有单个柱线的联合足以确保正确对齐:

#include <iostream>

#define BARS_IN_FOO 5
// X is the type of the "array", Y the type of its initializer, n the size
template<class X, class Y = X, int n = 1>
class InitArr {
union {
X initial;                          // guarantee correct alignment
char buffer[n * sizeof(X)];         // only to reserve enough place
};
public:
InitArr(Y& inival): initial(inival) {
for (int i = 0; i < n; i++) {
new(&initial + i)X(inival);     // properly construct an X at &initial +i
// which is guaranteed to be inside buffer
}
}
X& operator[] (int i) {                 // make InitArr behave like an array
X* arr = &initial;
return arr[i];                      // could add control that  0 <= i < n
}
};
struct Foo;
struct Bar { Foo & foo; Bar(Foo & foo) : foo{ foo } {} }; //Cannot be default initialized
struct Foo {
InitArr<Bar, Foo, BARS_IN_FOO> myBars;
Foo() :
myBars{ *this }
//I want to initialize all Bars with Bar{*this} 
{}
};
int main() {
Foo foo;
std::cout << &foo << std::endl;
// shows that all foo.myBars[i] point to the original foo
for (int i = 0; i < BARS_IN_FOO; i++) {
std::cout << &(foo.myBars[i].foo) << std::endl;
}
return 0;
}

由于 X 是构造到位的,任何 C++11 编译器都可以接受所有内容,并且您将获得一个真正的随机访问容器。没有未定义的行为,因为内存是由 char 数组保留的,并且对象在那里正确构造。这是一个通用技巧,可以在构造函数中延迟具有语言全部功能的复杂成员初始化,当仅使用初始值设定项或编译时元编程变得难以做到时。


这是我留在这里供参考的原始想法,但真的不太好......

您可以尝试构建一个自定义递归数组,以允许从相同的值初始化其所有元素:

// X is the type of the "array", Y the type of its initializer, n the size
template<class X, class Y=X, int n = 1>
class RArr {
public:
X first;
RArr<X, Y, n-1> other;
RArr(Y& i) : first(i), other(i) {}
X& operator [] (int i) {
if (i >= n || i < 0) throw std::domain_error("Out of range");
if (i == 0) return first;
return other[i - 1];
}
};
// specialization for n=1
template <class X, class Y> 
class RArr<X, Y, 1> {
public:
X first;
RArr(Y& i) : first(i) {}
X& operator [] (int i) {
if (i != 0) throw std::domain_error("Out of range");
return first;
}
};
struct Foo;
struct Bar { Foo & foo; Bar(Foo & foo) : foo{ foo } {} }; //Cannot be default initialized
struct Foo {
RArr<Bar, Foo, BARS_IN_FOO> myBars;
Foo() :
myBars{ *this } 
//I want to initialize all Bars with Bar{*this} 
{}
};
int main() {
Foo foo;
std::cout << &foo << std::endl;
// shows that all foo.myBars[i] point to the original foo
for (int i = 0; i < BARS_IN_FOO; i++) {
std::cout << &(foo.myBars[i].foo)  << std::endl;
}
return 0;
}

好吧,它可以根据您的要求工作,但毫无用处,因为所有Bar元素都引用相同的Foo。只有当类包含其他成员时Bar它才有意义。