我有以下课程:
//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
的数量在编译时是已知的,但我只想在一个地方指定这个数字(最好在定义中,欢迎其他建议)。
Foo::mybars
的用法:
Foo
和Bar
之间的关系是作文的关系。 Foo
由Bar
制成,其生命周期相同。 Bar
Foo
没有Foo
,反之亦然。Bar
和Foo
类是性能关键代码的一部分,它们的大小将影响程序的内存占用量(80-90%的内存占用量为{{1} } s和Bar
s)。编译器: MSVS2017版本15.3
修改:如果有帮助,将std::array<Bar, BARS_IN_FOO> myBars;
更改为Bar myBars[BARS_IN_FOO];
也是一件好事。
重要编辑:Bar
和Foo
的所有主持人都是公开的。这是一个错误。我改变了这个。
答案 0 :(得分:5)
如果您可以假设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)) {}
这可以很容易地重构为使用&#34;重复&#34;的更通用的模板元编程实用程序。我怀疑任何模板元编程库都会有一些这样的实用程序,在这里我不会因为无论如何都是一个内容而烦恼。但是如果遇到这样的问题,通常需要考虑(只需编写一个函数,返回一个N-entry初始化列表,所有这些都使用相同的表达式)。
直播示例:http://coliru.stacked-crooked.com/a/aab004c0090cc144。抱歉,无法轻松访问MSVC编译器。由于我不需要任何17个功能,所以还编译了14个。
编辑:即使Bar
不可移动/可复制,或者您认为事情不会得到优化,实际上也可以修改此技术以便在这种情况下工作。您可以生成array<std::reference_wrapper<Foo>, N
。但这有点复杂,通常在C ++中,大多数东西都应该是可移动的,通常构造函数调用不在关键路径中。如果有必要,我还可以详细说明。
Edit2:另外,请不要使用#define。 constexpr static auto BARS_IN_FOO = 5;
应该以完全相同的方式工作,除了它是正确的命名空间(可能还有一些我遗忘的其他宏观肮脏)。
答案 1 :(得分:1)
解决方案是构建一个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
类包含其他成员时才有意义。