1

我一直在考虑使用面向对象的方法来验证函数/方法接收的参数集的解决方案。例如,在以下代码段中,参数在使用前会“手动”检查。

InstallData::InstallData(std::string appPath, std::string appName,   
                         std::string errMsg) {
    if(appPath.empty()) {
       #ifndef NDEBUG
       std::cout << "Path not specified" << std::endl;
       #endif
    }
    if(appName.empty()) {
       #ifndef NDEBUG
       std::cout << "Application name not specified" << std::endl;
       std::cout << "Defaulting to AppName" << std::endl;
       this->appName = "AppName";
       #endif
    }
    if(errMsg.empty()) {
       #ifndef NDEBUG
       std::cout << "Error message not specified" << std::endl;
       std::cout << "Defaulting to Error" << std::endl;
       this->errMsg = "Error";
       #endif
    }
    // ... further initialization beyond this point ...
}  

随着参数数量的增加,验证代码的大小也会增加。我想到了一种检查参数(字符串和指针)的基本方法,即它们是空的还是空的(目的是使提供功能的代码更具可读性)。

class Validator {
public:
  bool validateStrs(std::vector<std::string> strings, std::vector<std::string> messages, bool quiet);
  bool validateStr(std::string str, std::string message, bool quiet);
  bool validatePtrs(std::vector<void*> ptrs, std::vector<std::string> messages, bool quiet);
  bool validatePtr(void* ptr, std::string message, bool quiet);
};

验证方法 validateStrs 和 validatePtrs 检查第一个数组的每个元素是否为空或 null 并显示来自第二个数组的消息(第一个数组的元素和第二个数组的元素之间存在一对一关系)如果安静标志是没有设置。
在我的实现中,这看起来像:

InstallData::InstallData(std::string appPath, std::string appName, 
 std::string errMsg, std::string errTitle) {
 // Initialize string container
 std::vector<std::string> strings;
 strings.push_back(appPath);
 strings.push_back(appName);
 strings.push_back(errMsg);
 strings.push_back(errTitle);
 // Initialize name container
 std::vector<std::string> names;
 names.push_back("ApplicationPath");
 names.push_back("ApplicationName");
 names.push_back("ErrorMessage");
 names.push_back("ErrorTitle");

 boost::shared_ptr<Validator> valid(new Validator());

 bool result = true;
     #ifndef NDEBUG
 result = valid->validateStrs(strings, names, false);
     #else
 result = valid->validateStrs(strings, names, true);
     #endif
 if(result){
     this->appPath = appPath;
     this->appName = appName; 
     this->errMsg = errMsg;
     this->errTitle = errTitle;
 } else {
     std::exit(0);
 }
}

消息也可以放在单独的文件中,从而使方法体更干净。
数值范围检查器也可以类似地实现。然而,这种方法不考虑参数之间的依赖关系。

是否有更优雅的解决方案来实现参数验证机制,可能使用模板?

4

2 回答 2

4

一种更优雅的方法是不对参数使用标准类型,而是定义特定的类来检查构造时的参数。就像是

class InvalidAppPath {};

class AppPath {
  public:
    AppPath(const std::string & appPath) : path(appPath) {
      if ( appPath.empty() ) throw InvalidAppPath();
    }
    operator std::string() { return path; }
  private:
    std::string path;
};

这也将更容易确保AppPath仅在构造和可能的修改时检查 an 的有效性。

这些幻灯片来自 Ric Parkin 在 2007 ACCU 会​​议上的演讲,更详细地探讨了这个想法。

于 2013-08-22T19:46:50.133 回答
1

也许您会发现利用函数名重载和可变参数模板更容易。您可以将要验证的参数信息与纠正措施一起分组到一个std::tuple. 我在IDEONE上实现了这个想法的一个小演示。

bool validate (std::string s) { return !s.empty(); }
bool validate (const void *p) { return p; }

template <typename Tuple>
bool validate (Tuple param) {
    if (validate(std::get<0>(param))) return true;
    #ifndef NDEBUG
    std::cout << "Invalid: " << std::get<1>(param) << std::endl;
    std::get<2>(param)();
    #endif
    return false;
}

bool validate () { return true; }

template <typename T, typename... Params>
bool validate (T param, Params... params) {
    return validate(param) & validate(params...);
}

然后,您可以像这样使用它:

bool result
    = validate(
          std::make_tuple(appPath, "ApplicationPath",
                          [&](){ appPath = "defaultPath"; }),
          std::make_tuple(appName, "ApplicationName",
                          [&](){ appName = "defaultName"; })
               //...
              );
于 2013-08-22T19:12:56.930 回答