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

C++ Initializing array member with a length known by constructor at compile time

本文关键字:编译 构造函数 数组 初始化 组成员      更新时间:2023-10-16

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

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

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需要在类声明中声明的数组大小,但是屏幕将具有不同量的按钮。我不相信由于currentScreen

,我不相信可以模板

是否有一种方法可以让一个数组在其声明/compile-time上找到数组大小?

这可以通过利用C 17的类模板参数扣除来完成。通过将Screen变成将std::size_t作为非类型模板参数的模板。然后,通过将参数数传递给构造函数,您可以将其用作模板参数的值。然后,这使您可以在Screen中制作一个大小的数组。这意味着每个 Screen hass都有不同数量的按钮是一种不同的类型,但是如果您需要将多个 Screen s存储在均匀的容器中,则可以从基类继承。

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

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"}};
}

以下代码演示了如何使用基类和虚拟函数,因此您可以使用指向基类的指针与不同的screens。

进行交互
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();
}

做到这一点的一种方法是使用一个模板构造函数通过参考来进行数组:

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"), ...});之类的语法,因为在构造函数之后将破坏任何临时数组。