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

时间:2017-08-21 14:03:42

标签: c++ arrays constructor

我有以下课程:

//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::myBarsBar的数量在编译时是已知的,但我只想在一个地方指定这个数字(最好在定义中,欢迎其他建议)。

Foo::mybars的用法:

  • 它的大小在运行期间永远不会改变但是我还没有确定它的数量是什么,但是会在[1..10](或左右)范围内(数字在开发过程中可能会发生很大变化)。
  • 元素值永远不会改变 (总是将是相同的非const对象)。
  • FooBar之间的关系是作文的关系。 FooBar制成,其生命周期相同。 Bar Foo没有Foo,反之亦然。
  • BarFoo类是性能关键代码的一部分,它们的大小将影响程序的内存占用量(80-90%的内存占用量为{{1} } s和Bar s)。

编译器: MSVS2017版本15.3

修改:如果有帮助,将std::array<Bar, BARS_IN_FOO> myBars;更改为Bar myBars[BARS_IN_FOO];也是一件好事。

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

2 个答案:

答案 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类包含其他成员时才有意义。