【问题标题】:C++: How to pass user input through the system without using global variables?C++:如何在不使用全局变量的情况下通过系统传递用户输入?
【发布时间】:2018-01-14 17:48:49
【问题描述】:

我遇到了问题,我的应用程序可能有很多用户输入,这些输入决定了应用程序的运行方式。该应用程序是一个内存数据库系统,例如,用户可以使用诸如“--pagesize 16384”(设置要使用的内存页面大小)、“--alignment 4096”(设置要使用的内存对齐)之类的命令调用该程序或“--measure”(设置标志以测量某些例程)。

目前我将所有用户输入保存在全局变量中,这些变量在头文件中定义为 extern:

//@file common.hh
extern  size_t      PAGE_SIZE_GLOBAL;
extern  size_t      ALIGNMENT_GLOBAL;
extern  size_t      MEMCHUNK_SIZE_GLOBAL;
extern  size_t      RUNS_GLOBAL;
extern  size_t      VECTORIZE_SIZE_GLOBAL;
extern  bool        MEASURE_GLOBAL;
extern  bool        PRINT_GLOBAL;
extern  const char* PATH_GLOBAL;

在主源文件中:

#include "modes.hh"

size_t      PAGE_SIZE_GLOBAL;
size_t      ALIGNMENT_GLOBAL;
size_t      MEMCHUNK_SIZE_GLOBAL;
size_t      RUNS_GLOBAL;
size_t      VECTORIZE_SIZE_GLOBAL;
bool        MEASURE_GLOBAL;
bool        PRINT_GLOBAL;
const char* PATH_GLOBAL;

int main(const int argc, const char* argv[]){

    ...
    //Initialize the globals with user input
    PAGE_SIZE_GLOBAL        = lArgs.pageSize();
    ALIGNMENT_GLOBAL        = lArgs.alignment();
    MEMCHUNK_SIZE_GLOBAL    = lArgs.chunkSize();
    RUNS_GLOBAL             = lArgs.runs();
    VECTORIZE_SIZE_GLOBAL   = lArgs.vectorized();
    MEASURE_GLOBAL          = lArgs.measure();
    PRINT_GLOBAL            = lArgs.print();
    std::string tmp         = lArgs.path() + storageModel + "/";
    PATH_GLOBAL             = tmp.c_str();

    ...
}

然后我在每个文件中包含头文件 common.hh,其中需要一个全局变量(可能在系统中非常深入)。

我已经阅读了十几遍以防止全局变量,所以这显然是不好的风格。在史蒂夫·麦康奈尔 (Steve McConnell) 的“代码完成 2”一书中,关于全局变量的章节也指出要防止全局变量并改用访问例程。在“如何使用访问例程”一节中,他写道

"隐藏类中的数据。使用 static 关键字声明该数据 (...) 以确保仅存在一个数据实例。写 让您查看数据并对其进行更改的例程。”

首先,全局数据不会改变(也许稍后会改变,但至少在不久的将来不会改变)。但我不明白这些访问例程如何更好?我还将在需要数据的每个文件中包含一个类。唯一的区别是全局数据是通过 getter 函数访问的静态成员。

(已编辑)我还考虑过使用全局数据单例类。但是一个包含所有全局数据的对象听起来有点矫枉过正,因为在它的不同目的地只需要对象的几个全局变量。

我的问题:我应该坚持使用全局变量吗?有没有更好的解决方案,我错过了什么?最佳做法是什么?

编辑: 如果我要确定最需要用户输入的几个类,我可以将全局数据更改为成员变量。将用户输入传递给这些类的最佳实践是什么?将数据作为参数通过整个系统传递到最低层听起来是错误的。是否有适合这里的设计模式(考虑类似工厂的东西)?

【问题讨论】:

  • 好吧,如果您使用单例类,您实际上不必传递它。方法可以执行类似Singleton::GetInstance() 的操作,这是一个返回Singleton& 的静态方法。不过,理想情况下,在 OO 解决方案中,您的数据和方法属于同一个类,因此几乎不会涉及传递。
  • 我认为“在不同的目的地只需要对象的几个全局变量”这句话是你真正问题的线索。拥有一组全局变量(或一个仅作为一组全局变量而存在的类)意味着您的其他类最终依赖于全局变量。您认为充满全局变量的单例类并没有好多少的直觉基本上是正确的。这只是重新组织问题。考虑为每个类只提供它需要的最小数据集,即使这会引入一些冗余。更容易理解 + 稍后进行测试。
  • @patatahooligan 我的错,谢谢。即使我能找到一个数据最适合的类,我如何让用户输入这个类?
  • @struthersneil 如何将用户输入(来自 main)传递给实际需要数据的不同类?除了将数据作为参数传递给整个系统之外,还有其他方法吗?我的架构中有不同的层,例如,我不能直接从 main 转到内存管理器。内存管理器需要知道使用的页面大小、对齐方式等。
  • 将数据作为参数传递给整个系统并不像听起来那么糟糕。我不确切知道您是如何设置内存管理器的,但假设您的内存管理器是 main 在某个时候实例化的对象,我会将必要的配置传递给构造函数。然后你就有了一个内存管理器,它很容易在不同的配置下单独测试。

标签: c++ global-variables extern


【解决方案1】:

如何在不使用全局的情况下通过系统传递用户输入 变量。

这很容易。惊喜,我创建了一个类。

有一段时间,我把这个类称为旅行箱,因为我认为它类似于旅行中的手提箱需求。 TC_t 是一个非标准容器,它为您的目的地所发生的事情保存有用的东西,并且只创建了一个,并将引用传递给可以使用该信息的任何其他对象。不是全球性的,在最严格的意义上。

这个 TC_t 是在 main() 线程中创建的,同时研究命令行选项。


我最近写了又一个人生游戏。用户输入包括 a) 输出目的地(即 tty 编号),b) 初始填充模式选择,c) 游戏板尺寸的“覆盖”,d) 测试模式,包括最大速度,以及单元格的向量与数组选项行为。

GOLUtil_t(Game Of Life Utility)(以前的 TC_t)包括在不止一项工作中有用的方法。

对于您的问题,我避免使用的两个典型全局变量是 a) gameBoard 和 b) ansi 终端访问。

std::cout << "accessing '" << aTermPFN << "' with std::ofstream " 
          << std::endl;
std::ofstream*  ansiTerm = new std::ofstream(aTermPFN);

if (!ansiTerm->is_open())
{
   dtbAssert(nullptr != ansiTerm)(aTermPFN);
   std::cerr << "Can not access '" << aTermPFN << "'" << std::endl;
   assert(0);  // abort
 }

// create game-board - with a vector of cell*
CellVec_t  gameBoard;
gameBoard.reserve (aMaxRow * aMaxCol);

GOLUtil_t  gBrd(aMaxRow, aMaxCol, gameBoard, *ansiTerm);

最后一行调用了 GOLUtil_t 的 ctor。

然后实例“gBrd”被(通过引用)传递给游戏的 ctor,并从那里传递给它包含的任何聚合对象。

std::string retVal;
{
  // initialize display, initialize pattern
  GameOfLife_t  GOL(gBrd, timeOfDay, fillPatternChoiceLetter, useArray);

  std::string retValS = GOL.exec2(testMode);

  retVal = gBrd.clearGameBoard(retValS); // delete all cells
}
// force GameOfLife_t dtor before close ansiTerm

ansiTerm->close();

总结 - 没有全局变量。

需要此信息的任何类的每个实例(输出到哪里?维度是多少?)在其整个生命周期内都可以访问 GOLUtil_t。 GOLUtil_t 有减轻编码负担的方法。

注意:因为是单输出终端,所以我用的是单线程(main)


您的第一个重构工作可能是:

a) 移除全局类,

b) 而是在 main() 中实例化它们(用于生命周期控制)

c) 然后将这些以前的全局实例通过引用传递给那些使用它们的非全局对象。我在 ctor(s) 中推荐。

d) 记得清理(如果是新的则删除)


我的环境:Ubuntu 15.10,64 位,g++ V5

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2019-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多