创建一个函数,使用模板创建 class 或子对象 class 的对象
Making a function that creates an object of a class OR child class using templates
我有一个名为 Menu
的父 class,它负责以格式化的方式将其属性显示到控制台。我还有这个 Menu
class 的一些子 classes,它们可以以不同的方式显示附加信息或相同信息。这是一些示例代码:
#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
的 class,它管理 Menu
以及一些其他对象(其他类型)。为了节省不需要创建的菜单的内存,我目前将此 class 的 Menu
属性作为 std::unique_ptr<Menu>
在用户访问给定菜单时重新分配(例如,商店游戏中的菜单、餐厅的菜单、书店的选择列表等)。 ConsoleUI
class 管理实现这些特定菜单。
为了使这个更容易处理,而不是每次我想去一个新菜单时都输入“m_menu = std::make_unique<DescMenu>(DescMenu("Title", { "option1", "option2", "option3" }, "Prompt: "));
”,我想在 class 中创建一个私有方法可以为需要创建的对象获取所需数量的参数,然后根据参数构造的对象将 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
中的菜单系统,我会喜欢在评论或答案中提出建议,这些建议也提供了一种方法来完成我最初提出的问题(或解释为什么这样做不可能)。
这里的问题是 braced-init-list {...}
,它不适用于类型推导。
一个可能的解决方案是明确说明:
reassign_menu<DescMenu>(
"Shop", "Select something you want to buy",
std::vector<std::string>{"Cereal", "Bowl", "Knife", "Gun", "Steak"}
).run();
另一种选择是更改构造函数中参数的顺序,使 std::initializer_list
成为第一个参数:
DescMenu(std::vector<std::string> options, std::string title, std::string info,
std::string prompt = "Enter: ") : ...
template<typename MenuTy, typename... Args>
const Menu& reassign_menu(std::initializer_list<std::string> il, Args... args) {
m_menu = std::make_unique<MenuTy>(il, args...);
return *m_menu;
}
...
reassign_menu<DescMenu>({"Cereal", "Bowl", "Knife", "Gun", "Steak"},
"Shop", "Select something you want to buy").run();
后一种方法在标准库中被广泛使用。例如,参见 std::optional<T>::emplace
.
的声明
补充说明:
- 如果你想对
std::initializer_list
类型通用(使其成为 T
),请注意 std::vector<std::string>
不能直接从 std::initializer_list<const char*>
构造。您可以使用带有一对迭代器的构造函数:std::vector<std::string>(il.begin(), il.end())
.
std::make_unique<MenuTy>(MenuTy(args...));
可以简化为std::make_unique<MenuTy>(args...);
.
ShopMenu()
和BookStoreMenu()
标记为const
。您不能在这些函数中修改 m_menu
。 const
应删除(或 m_menu
应设为 mutable
)。
我有一个名为 Menu
的父 class,它负责以格式化的方式将其属性显示到控制台。我还有这个 Menu
class 的一些子 classes,它们可以以不同的方式显示附加信息或相同信息。这是一些示例代码:
#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
的 class,它管理 Menu
以及一些其他对象(其他类型)。为了节省不需要创建的菜单的内存,我目前将此 class 的 Menu
属性作为 std::unique_ptr<Menu>
在用户访问给定菜单时重新分配(例如,商店游戏中的菜单、餐厅的菜单、书店的选择列表等)。 ConsoleUI
class 管理实现这些特定菜单。
为了使这个更容易处理,而不是每次我想去一个新菜单时都输入“m_menu = std::make_unique<DescMenu>(DescMenu("Title", { "option1", "option2", "option3" }, "Prompt: "));
”,我想在 class 中创建一个私有方法可以为需要创建的对象获取所需数量的参数,然后根据参数构造的对象将 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
中的菜单系统,我会喜欢在评论或答案中提出建议,这些建议也提供了一种方法来完成我最初提出的问题(或解释为什么这样做不可能)。
这里的问题是 braced-init-list {...}
,它不适用于类型推导。
一个可能的解决方案是明确说明:
reassign_menu<DescMenu>(
"Shop", "Select something you want to buy",
std::vector<std::string>{"Cereal", "Bowl", "Knife", "Gun", "Steak"}
).run();
另一种选择是更改构造函数中参数的顺序,使 std::initializer_list
成为第一个参数:
DescMenu(std::vector<std::string> options, std::string title, std::string info,
std::string prompt = "Enter: ") : ...
template<typename MenuTy, typename... Args>
const Menu& reassign_menu(std::initializer_list<std::string> il, Args... args) {
m_menu = std::make_unique<MenuTy>(il, args...);
return *m_menu;
}
...
reassign_menu<DescMenu>({"Cereal", "Bowl", "Knife", "Gun", "Steak"},
"Shop", "Select something you want to buy").run();
后一种方法在标准库中被广泛使用。例如,参见 std::optional<T>::emplace
.
补充说明:
- 如果你想对
std::initializer_list
类型通用(使其成为T
),请注意std::vector<std::string>
不能直接从std::initializer_list<const char*>
构造。您可以使用带有一对迭代器的构造函数:std::vector<std::string>(il.begin(), il.end())
. std::make_unique<MenuTy>(MenuTy(args...));
可以简化为std::make_unique<MenuTy>(args...);
.ShopMenu()
和BookStoreMenu()
标记为const
。您不能在这些函数中修改m_menu
。const
应删除(或m_menu
应设为mutable
)。