【发布时间】:2019-12-09 03:26:16
【问题描述】:
我有一个名为Menu 的父类,它负责将其属性以格式化的方式显示到控制台。我还有一些 Menu 类的子类,它们可以以不同的方式显示附加信息或相同信息。下面是一些示例代码:
#include <iostream>
#include <string>
#include <vector>
// Using just to make below code shorter for SO
using std::cout, std::string, std::vector;
class Menu
{
protected:
string m_title;
vector<string> m_options;
string m_prompt;
public:
Menu(string title, vector<string> options, string prompt = "Enter: ") :
m_title(title), m_options(options), m_prompt(prompt)
{}
/**
Displays the members of this object in a formatted way
*/
virtual void run() const
{
// Display title
cout << m_title << '\n';
// Make a dashed underline
for (const auto& ch : m_title)
{
cout << '-';
}
cout << "\n\n";
// Display options
for (int i = 0; i < m_options.size(); i++)
{
cout << '(' << (i + 1) << ") " << m_options.at(i) << '\n';
}
cout << '\n';
// Display prompt
cout << m_prompt << std::flush;
}
};
/**
Subclass of menu; allows for an "info" line below the title underline that
gives instruction to the user
*/
class DescMenu : public Menu
{
private:
string m_info;
public:
DescMenu(string title, string info, vector<string> options,
string prompt = "Enter: ") : Menu(title, options, prompt),
m_info(info)
{}
void run() const override
{
cout << m_title << '\n';
for (const auto& ch : m_title)
{
cout << '-';
}
// Display extra info
cout << '\n' << m_info << '\n';
for (int i = 0; i < m_options.size(); i++)
{
cout << '(' << (i + 1) << ") " << m_options.at(i) << '\n';
}
cout << '\n';
cout << m_prompt << std::flush;
}
};
/**
A trivial subclass of menu; does not display the dashed underline under title
*/
class NoDashMenu : public Menu
{
public:
NoDashMenu(string title, vector<string> options,
string prompt = "Enter: ") : Menu(title, options, prompt)
{}
void run() const override
{
cout << m_title << "\n\n";
for (int i = 0; i < m_options.size(); i++)
{
cout << '(' << (i + 1) << ") " << m_options.at(i) << '\n';
}
cout << '\n';
cout << m_prompt << std::flush;
}
};
我还有一个名为ConsoleUI 的类,它管理Menu 以及一些其他对象(其他类型)。为了节省不需要创建的菜单的内存,我目前将此类的Menu 属性作为std::unique_ptr<Menu> 在用户访问给定菜单时重新分配(例如,游戏中的菜单用于商店,或餐厅的菜单,或书店的选择列表等)。 ConsoleUI 类管理实现这些特定的菜单。
为了使这更容易处理,我不想每次想进入新菜单时都输入“m_menu = std::make_unique<DescMenu>(DescMenu("Title", { "option1", "option2", "option3" }, "Prompt: "));”,而是想在类中创建一个私有方法,该方法可以接收所需数量的参数对于需要创建的对象,然后根据参数构造的对象将m_menu重新分配给Menu对象(或其子对象之一)。我还希望不必为每个对象类型的每个构造函数指定重载。对于一个概念示例,这里是一些示例代码及其构建错误(在末尾注释):
#include <memory> /* This is line 108 and comes after the code above (for build log reference) */
class ConsoleUI
{
private:
using Menu_ptr = std::unique_ptr<Menu>;
Menu_ptr m_menu;
string m_other1;
unsigned m_other2;
double m_other3;
template <typename MenuTy, typename... ConArgs>
/**
Makes a 'Menu' object or child-class object with the given parameters
@returns A reference to the newly created menu
*/
const Menu& reassign_menu(ConArgs... constructor_args)
{
m_menu = std::make_unique<MenuTy>(MenuTy(constructor_args...));
return *m_menu;
}
public:
/* ... Constructor Here ... */
void ShopMenu() const
{
//
// IntelliSense detects the following error for the below code:
// ------------------------------------------------------------
// no instance of function template "ConsoleUI::reassign_menu"
// matches the argument list -- argument types are: (const char [5],
// const char [33], {...})
//
reassign_menu<DescMenu>(
"Shop",
"Select something you want to buy",
{ "Cereal", "Bowl", "Knife", "Gun", "Steak" }
).run();
/* ... Collect input, etc ... */
}
void BookStoreMenu() const
{
//
// Same IntelliSense error below...
//
reassign_menu<NoDashMenu>(
"Book Store",
{ "Harry Potter", "Narnia", "Check the back ;)" }
).run();
/* ... Do more stuff ... */
}
};
//
// g++ build log (autogenerated via Code Runner on VS Code):
//
// PS C:\Dev\C++\Others\DevBox\Testing> cd "c:\Dev\C++\Others\DevBox\Testing\src\" ; if ($?) { g++ -std=c++17 TestScript.cpp -o TestScript } ; if ($?) { .\TestScript }
// TestScript.cpp: In member function 'void ConsoleUI::ShopMenu() const':
// TestScript.cpp:148:9: error: no matching function for call to 'ConsoleUI::reassign_menu<DescMenu>(const char [5], const char [33], <brace-enclosed initializer list>) const'
// ).run();
// ^
// TestScript.cpp:126:17: note: candidate: 'const Menu& ConsoleUI::reassign_menu(ConArgs ...) [with MenuTy = DescMenu; ConArgs = {}]'
// const Menu& reassign_menu(ConArgs... constructor_args)
// ^~~~~~~~~~~~~
// TestScript.cpp:126:17: note: candidate expects 0 arguments, 3 provided
// TestScript.cpp: In member function 'void ConsoleUI::BookStoreMenu() const':
// TestScript.cpp:161:9: error: no matching function for call to 'ConsoleUI::reassign_menu<NoDashMenu>(const char [11], <brace-enclosed initializer list>) const'
// ).run();
// ^
// TestScript.cpp:126:17: note: candidate: 'const Menu& ConsoleUI::reassign_menu(ConArgs ...) [with MenuTy = NoDashMenu; ConArgs = {}]'
// const Menu& reassign_menu(ConArgs... constructor_args)
// ^~~~~~~~~~~~~
// TestScript.cpp:126:17: note: candidate expects 0 arguments, 2 provided
//
显然这不起作用,所以我很好奇是否有办法做到这一点。此外,如果有人有更好的想法来管理 ConsoleUI 中的菜单系统,我会喜欢 cmets 中的建议或答案,这也提供了一种方法来做我最初要求的事情(或解释为什么它不是不可能)。
【问题讨论】:
-
"使用只是为了让下面的代码缩短 SO" - 请不要这样做,尤其是对于 SO。 :)
-
这里的代码太多了。只需查看 minimal reproducible example 以获取有关代码示例外观的指南。
标签: c++ oop templates polymorphism generic-programming