我正在寻找使用c ++进入程序主要功能的最佳实践提示。目前我认为有两种方法可行。 (尽管这些方法的“边缘”可以任意地彼此接近)
1:编写一个“Master”类,它接收传递给main函数的参数并处理“Master”类中的完整程序(当然你也使用其他类)。因此,主要功能将减少到最小的线。
#include "MasterClass.h"
int main(int args, char* argv[])
{
MasterClass MC(args, argv);
}
2:当然,在主函数中编写“完整”程序,利用用户定义的对象!然而,还涉及全局函数,主要功能可能会有所增加。
我正在寻找一些关于如何用c ++编写程序主要功能的一般指导原则。我通过尝试为第一种方法编写一些单元测试来遇到这个问题,这有点困难,因为大多数方法都是私有的。
答案 0 :(得分:23)
为什么你会有一个大师班?它的单一责任区域是什么?
“大师”或“应用程序”类往往会变成一个大块,可以做太多不同的事情。最终,重点是什么?它给你买了什么?
为什么不使用主要功能来提供主要功能?在main
中定义高级应用程序生命周期。它需要一些命令行参数并解析它们(最好通过将其委托给另一个函数或类),然后它调用一些设置功能,然后它可能会进行某种主循环,然后再进行一些清理。总而言之,这可能会给你一个大概10-15行的主要功能,但可能不会超过这个。它应该尽可能地委托给其他类和函数。所以main
本身保持简短和甜美,但仍然有目的。
将这种超高级流量放在main
中意味着很容易找到,因为main
无论如何都是你的起点。如果您想了解代码,那么您将开始寻找它。因此,在尝试理解代码时,请将读者想知道的内容放入其中。
当然,你可以把所有这些都放在一个“主要课堂”中,除了满足所有那些觉得“一切都必须在课堂上”的Java-luddites之外你什么都没有获得。
答案 1 :(得分:7)
你正在描述两种“极端”方法,这些方法对我来说都不合适。没有一个God Class,也没有单一的神功能是实现任何旨在实际使用的非平凡程序的正确方法。
在MasterClass
中对main()
进行一次调用就可以了(虽然我更喜欢分区功能,例如在main()
中执行任何命令行特定处理,从命令行参数的细节中解耦MasterClass
。但是,如果该类难以进行单元测试,则表明存在设计问题,通常解决方案是将部分或全部可测试功能委托给其他类,通过公共接口可以轻松地对其进行单元测试。
您的第二种方法可能再次成为单元测试的问题,因此您应该努力extract methods从它(然后最好是move them into a separate class)进行细粒度单元测试。
因此,您想要的最佳位置介于两个极端之间,受到使您的代码可测试的要求的限制。
值得思考如何一般地构建程序,而不仅仅是main()
的上下文。基本思想是将其划分为“块”(类和方法)
答案 2 :(得分:3)
我会在主例程中分析进程的参数,然后通过传递比argc和argv更可读的参数来创建类实例。
答案 3 :(得分:2)
你的第一种方法很常见,虽然这个类往往被命名为“应用程序”(或至少包含“应用程序”一词),所以这样做。
答案 4 :(得分:2)
第一部分:我很少使用C ++,但我认为这不是一个特定于语言的问题 嗯,我想这可归结为除了一些实用性问题之外的味道。 我个人倾向于使用布局#1,但不要将命令行解析例程放在 MasterClass 中。对我来说,这显然属于主要的。 MasterClass 应该获得解析的参数(整数,FileStreams,等等)。
答案 5 :(得分:2)
通常我在应用程序的命名空间中调用一个main函数(具有相同的签名):
namespace current_application {
int main( int argc, char **argv ) {
// ...
}
}
int main( int argc, char **argv ) {
return current_application::main( argc, argv );
}
然后我通常使用我的实际main(命名空间中的那个)初始化应用程序明智的事物:
在标准输入/输出/错误上设置区域设置
解析应用程序参数
实例化我的主类的对象,如果存在(等同于QApplication
)
调用main函数(如果存在)(等同于QApplication::run
)
并且通常更喜欢在那里添加try
catch
块,以便在发生崩溃时打印更多调试信息。
然而,所有这些都是非常主观的;它是你编码风格的一部分。
答案 6 :(得分:1)
如果异常没有处理程序,则未指定是否在std::terminate
之前调用本地对象的析构函数。
如果你想让这种行为可以预测,那么main
是一个拥有最顶层异常处理程序的好地方,可以进行某种报告。
通常这就是我放入main
的所有内容,否则只会调用cppMain
...; - )
干杯&第h。,
答案 7 :(得分:0)
通常,我执行必要的特定于平台的设置操作,然后委托给对象。对象优于函数没有什么优势,但是对象可以从接口继承,如果你说平台无关的库使用接口实现进行回调,那么这些接口是很好的。
答案 8 :(得分:0)
您给出的示例只是将主函数移动到命名空间中。我没有看到这里的优势。道路中间的方法略显倾向于使用Master Class模型。 Master Class对象通常很大,最好在堆上创建。我有main函数创建对象并处理可能在此处发生的任何错误。
class MasterClass {
public:
static MasterClass* CreateInstance( int argc, char **argv );
// ...
}
int main(int argc, char** argv)
{
try
{
MasterClass mc = MC::CreateInstance(argc, argv);
}
catch(...)
{
// ...
}
}
这也具有以下优点:任何与实际程序逻辑无关的处理(例如,读取环境等)都不必放在MasterClass中。他们可以进入main()。一个很好的例子是Lotus Domino系统的服务器加载项任务。这里的任务只应在Domino调度程序任务将控制权移交给它时运行。这里的主要内容可能如下所示:
STATUS LNPUBLIC AddInMain(HMODULE hModule, int argc, char far *argv[])
{
MasterClass mc = MC::CreateInstance(argc, argv);
while(/* wait for scheduler to give control */)
{
STATUS s = mc->RunForTimeSlice();
if (s != SUCCESS)
break;
}
// clean up
}
因此,与调度程序交互的所有逻辑都是主要的,程序的其余部分不必处理任何逻辑。
答案 9 :(得分:0)
我更喜欢IOC(控制反转)方法。
因此我的“main”使用命令参数来确定程序的“选项”和“配置”。通过选项我的意思是解释在命令行中传递的某些标志,并且通过配置我的意思是加载配置文件。
它用于加载配置文件的对象有一个“运行”命令,但运行的内容(如果有的话)也取决于命令行参数。