C ++初始化数组成员,其长度由构造函数在编译时已知

时间:2019-02-19 19:44:48

标签: c++ c++17

我正在为嵌入式设备上的显示器编写代码,该设备可显示屏幕,每个屏幕都有一些按钮。屏幕之间的按钮数量不同,但是在编译时已知该数量。

现在,这是有关如何设置类的简化版本:

class Button;

class Screen {
private:
    Button *buttons;
    unsigned int buttonCount;
public:
    Screen(Button *_buttons, unsigned int _buttonCount)
        : button(_buttons), buttonCount(_buttonCount) {}
};

这是我如何使用它们的一个想法:

// For this example, Button has a constructor taking a string for
// the button's label
static Button buttonsForMainMenu[] = {
    Button("Do this"),
    Button("Do that"),
    Button("Exit")
};

Screen mainMenu (buttonsForMainMenu, 3);

Screen *currentScreen = &mainMenu;

int main() {
    // ...
    while (1) {
        currentScreen->show();
        // handle buttons, etc.
    }
}

我想避免为按钮使用单独的数组。理想情况是这样的:

Screen mainMenu ({
    Button("Do this"),
    Button("Do that"),
    Button("Exit")
});

这段代码是针对不需要动态内存分配的嵌入式系统的,因此我想避免这种情况。 std::array需要在类声明中的声明位置处具有数组大小,但是屏幕将具有不同数量的Button。我不相信可以使用currentScreen来创建Screen类的模板。

类是否可以在声明时/在编译时找到数组大小的数组?

2 个答案:

答案 0 :(得分:6)

这可以通过利用C ++ 17的Class template argument deduction来完成。通过将Screen变成一个以std::size_t作为非类型模板参数的模板。然后,通过获取传递给构造函数的参数数量,可以将其用作模板参数的值。然后,您可以在Screen中创建一个具有该大小的数组。这意味着每个具有不同按钮数量的Screen都是不同的类型,但是如果您需要在同质容器中存储多个Screen,则可以从基类继承它。

您可以看到使用此最小示例

struct button
{
    std::string text;
};

template<std::size_t N>
struct screen
{
    button buttons[N];
    // constrain Args to only being buttons
    template<typename... Args, std::enable_if_t<std::is_same_v<std::common_type_t<Args...>, button>, bool> = true>
    screen(Args&&... args) : buttons{std::forward<Args>(args)...} {}
};

// get the number of arguments and use it for the array size
template<typename... Args>
screen(Args... args) -> screen<sizeof...(Args)>;

int main(){
    screen a{button{"one"}};
    screen b{button{"one"}, button{"two"}};
    screen c{button{"one"}, button{"two"}, button{"three"}};
}

以下代码演示了如何使用基类和虚函数,以便您可以使用指向基类的指针与不同的screen进行交互。

struct button
{
    std::string text;
};

struct screen_interface 
{ 
    void virtual show() = 0; 
};

template<std::size_t N>
struct screen : screen_interface
{
    button buttons[N];
    // contstrain Args to only being buttons
    template<typename... Args, std::enable_if_t<std::is_same_v<std::common_type_t<Args...>, button>, bool> = true>
    screen(Args&&... args) : buttons{std::forward<Args>(args)...} {}
    void show()
    {
        for (auto const& e : buttons)
            std::cout << e.text << "\n";
    }
};

// get the number of arguments and use it for the array size
template<typename... Args>
screen(Args... args) -> screen<sizeof...(Args)>;

int main(){
    screen a{button{"one"}};
    screen b{button{"one"}, button{"two"}};
    screen c{button{"one"}, button{"two"}, button{"three"}};

    screen_interface* si = &b;
    si->show();
    si = &a;
    si->show();
}

答案 1 :(得分:2)

一种方法是使用模板构造函数通过引用获取数组:

template<size_t size>
Screen(Button (&_buttons)[size]): buttons(_buttons), buttonCount(size) {}

您将像这样使用它:

static Button buttonsForMainMenu[] = {
    Button("Do this"),
    Button("Do that"),
    Button("Exit")
};

Screen mainMenu(buttonsForMainMenu);

请注意,由于Screen仅具有指向按钮的指针,因此该数组的生存时间至少与屏幕一样长。因此,我认为不存在没有动态分配的Screen mainMenu({Button("Do this"), ...});这样的语法的方法,因为在构造函数之后,任何临时数组都将被破坏。