初始化在类类型 #define 中定义的非静态成员数组,不带默认 ctor
Initializing non-static member array of size defined in a #define of class type without default ctor
我有以下类:
//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
呢?Bar
s 的数量在编译时是已知的,但我只想在一个地方指定这个数字(最好在定义中,欢迎其他建议(。
Foo::mybars
的用法 :
- 它的大小在运行时永远不会改变,但我还没有决定数字是多少,但会在 [1..10](左右(范围内(数字在开发过程中可能会发生很大变化(。
- 元素值永远不会改变 (始终是相同的非常量对象(。
Foo
和Bar
之间的关系是组合的关系。Foo
是由Bar
组成的,它们的寿命是一样的。Bar
没有Foo
,反之亦然是没有意义的。Foo
和Bar
类是性能关键型代码的一部分,它们的大小将极大地影响程序的内存占用(80-90%的内存占用将Foo
秒和Bar
秒(。
编译器:MSVS2017版本15.3
编辑:如果这有帮助,将std::array<Bar, BARS_IN_FOO> myBars;
更改为Bar myBars[BARS_IN_FOO];
也可以。
重要编辑:Bar
和Foo
的所有 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
它才有意义。
- 为什么我无法访问指向数组中成员函数的指针?
- 如何将结构/联合数组的成员传递到函数中
- 使用带有参数包的数组的成员数组初始化类
- 有没有办法给数组的成员指定不同的名称?
- 使用包含结构的数组的成员函数
- C++:释放动态数组(结构成员)和指向此结构的指针的方法
- C 用数组作为成员创建对象
- 用另一个 constexpr 数组对成员数组进行大括号初始化
- 来自数组的成员和比特菲尔德的结构
- 如何创建指向数组类成员的指针?
- 将数组的成员设置为零
- "Empty"数组\向量成员 C++
- 功能指针数组(包括成员功能)投掷模板专业化错误
- 另一个结构内部的结构数组的成员变量没有通过引用传递
- 正确转换 2D 数组的成员变量
- 常量数组类成员初始化
- 为具有对象或对象数组作为成员的类运行不同的代码
- C++ 如何指向数组的成员
- 将包含数组数据成员的结构保存到文件
- 数组类成员初始化(C++)