【问题标题】:Data controlled programs in c++c++中的数据控制程序
【发布时间】:2016-04-26 20:06:35
【问题描述】:

不确定如何命名这个问题,因为问题本身正在寻找一个我不知道其名称的构造。

问题是我正在处理控制流很大程度上依赖于数据的程序。

例如,我创建了一个 MIPS 模拟器,它实现了超过 50 条指令的列表,每条指令都独立实现,并且所有内容都由一个巨大的 switch case 控制

switch (function){ //Function is an int, each function (eg SLL) is 
        case 0:    //associated with one
            if (state->debug_level > 0){
                fprintf(state->debug_out, "SLL\n");
            }
            step_err = SLL(state, rs, rt, rd, sa);
            break;
        case 2:
            if (state->debug_level > 0){
                fprintf(state->debug_out, "SRL\n");
            }
            step_err = SRL(state, rs, rt, rd, sa);
            break;
        case 3:
            if (state->debug_level > 0){
                fprintf(state->debug_out, "SRA\n");
            }
//

有人告诉我,这可以使用函数指针来实现,但要做到这一点,我正在寻找一种将任何类型的数据关联起来的方法,比如字符串与其他数据,比如整数。我知道地图,但不想推回每一对。我正在寻找某种类似数组的语法,我认为如果之前看过它可能看起来类似于这样:

¿type? function_codes[]{
     0, "SLL";
     2, "SRL";
     3, "SRA";
     ...
 }

我不是在寻找解决这个问题的方法,而是寻找一种通用的方法来引入数据之间的快速关系并使用它来修改控制流。

回答后编辑 我实际上在寻找但我不知道确实是地图,但特别是它的初始化语法类似于数组(请参阅接受的答案)。这与函数指针一起使用完成了所需的工作。

【问题讨论】:

  • 您正在寻找std::mapstd::unordered_map
  • 那不是std::array<return_type (*)(A0, A1, A2, A3, A4), max_function> functions 吗?
  • 运行时的数据控制行为听起来与语言解析非常相似。可能有点矫枉过正,但你有没有看过 BNF 解析器的方向?
  • 虽然您希望在运行时访问这些数据,但似乎这一切都是在编译时定义的。听起来像是模板的工作!

标签: c++ arrays list dictionary


【解决方案1】:

给出一些定义

#include <iostream>
#include <iterator>
#include <algorithm>
#include <stdexcept>
#include <map>

using namespace std;

struct state{
    int debug_level = 1;
    const char* debug_out = "%s";
} s;

// some functions to call
void SLL(state& s, int, int, int, int){
    cout << "SLL";
}
void SLR(state& s, int, int, int, int){
    cout << "SLR";
}
void SLT(state& s, int, int, int, int){
    cout << "SLT";
}

你可以使用地图

auto mappedname2fn = map<string, delctype(SLL)*>{
    {"SLL", SLL}, 
    {"SLR", SLR}
};

// call a map function
mappedname2fn["SLR"](s, 1, 2, 3, 4);

如果您不想要地图,您可以使用预先排序的数组进行二分搜索

这是一个名称、函数对数组的二进制搜索

template<typename P, int N, typename ...T> 
auto callFn(P(&a)[N], string val, T&&... params){
    auto it = lower_bound(a, a+N, make_pair(val, nullptr),
        [](auto& p1, auto& p2){return p1.first < p2.first;});
    if(it==(a+N) || val<it->first) throw logic_error("not found");
    return it->second(forward<T>(params)...);
}

所以你可以设置一个数组并使用它:-

// array sorted in alphabetical order for binary search to work
pair<string, decltype(SLL)*> name2fn[] = {
        {"SLL", SLL}, 
        {"SLR", SLR},
        {"SLT", SLT}
    };
void callFn(string name, state& s, int a, int b, int c, int d){
    try{
        callFn(name2fn, name, s, a, b, c, d);
    }
    catch(exception& e){
        cout << e.what();
    }
}

// call it  
callFn("SLL", s, 1, 2, 3, 4);

【讨论】:

    【解决方案2】:

    正如您所猜测的,函数指针实际上是一种很好的方法。由于您指定不想使用 Map,因此您将使用函数指针数组实现基于整数的函数调度。请注意,由于我不知道您的 MIPS 函数(SLL、SRL 等)的类型签名,因此我使用了虚拟占位符类型名称。

    typedef ret_t (*mips_func)(arg1_t, arg2_t, arg3_t, arg4_t, arg5_t);
    
    mips_func function_codes[] = {
        &SLL,
        &SRL,
        &SRA,
        ...
    };
    
    //...Later, in the part of your code that used to contain the big switch statement
    step_err = (*function_codes[function])(state, rs, rt, rd, sa);
    

    语法&amp;SLL 获取指向函数SLL 的指针,我假设它已经在作用域内,因为您可以直接从switch 语句中调用它。

    请注意,这假定函数的数字代码是从 0 到 [最大代码值] 的连续整数序列。如果未使用某些数字代码,那么您将需要在数组中留下明确的间隙(通过在一个或多个条目中放置一个 NULL 指针)或使用 std::map&lt;int, mips_func&gt; 以便您可以使用任意非连续整数值作为键职能。幸运的是,使用 Map 仍然不需要 push_backing 每个元素,因为 C++ 现在有初始化列表。使用 Map 的相同代码如下所示:

    typedef ret_t (*mips_func)(arg1_t, arg2_t, arg3_t, arg4_t, arg5_t);
    
    std::map<int, mips_func> function_codes = {
        {0, &SLL},
        {2, &SRL},
        {4, &SRA},
        ...
    };
    
    //Using the Map looks exactly the same, due to its overloaded operator[]
    step_err = (*function_codes[function])(state, rs, rt, rd, sa);
    

    【讨论】:

    • 正是我要找的东西,也很简单,所以谢谢!我将编辑我的问题,以便为下一位观众提供更准确的信息
    【解决方案3】:

    您可以将数据分组为跨结构具有相同名称的静态成员,然后使用模板进行一般访问:

    struct A { auto call() const { return "((1))"; };  static const char * name; };
    struct B { auto call() const { return "{{2}}"; };  static const char * name; };
    struct C { auto call() const { return "<<3>>"; };  static const char * name; };
    // n.b. these `T...` have: `sizeof(T) == ... == sizeof(empty_struct)`
    
    const char * A::name = "A";
    const char * B::name = "B";
    const char * C::name = "C";
    

    boost::variant(以及即将实现的std::variant)实现了一个类型安全的联合,它提供了一种将这些结构用作值的非常干净和有效的方式:

    #include <cstdio>
    #include <vector>
    #include <boost/variant.hpp>
    
    int main()
    {
        std::vector<boost::variant<A, B, C>> letters{A{}, B{}, C{}, B{}, A{}};
        auto visitor = [](auto x) { std::printf("%s(): %s\n", x.name, x.call()); };
        for (auto var : letters) { boost::apply_visitor(visitor, var); }
    }
    

    Demo

    【讨论】:

      【解决方案4】:

      为了简化,您可以使用关联容器。如果顺序很重要,则使用std::map,或者在其他情况下使用std::unordered_map

      而且你可以使用与想要的语法相似的语法

      std::map<size_t, std::string> codes_map = decltype(codes_map) {
          { 0, "val1" },
          { 1, "val2" }
      };
      

      【讨论】:

      • 这并不能真正回答问题。此外,OP 明确表示他不想使用地图。
      • 当然,你是对的。但另一方面,可以将所选语法与映射一起使用,例如 std::map&lt;std::string, std::string&gt; codes_map = decltype(codes_map){ {"key1", "val1"}, {"key2", "val2"} };
      • @Edward 不,OP 说“我知道地图,但不想推迟每一对”- 他们不必这样做,因为我们有 @ 987654325@ 这个现代时代的语法,并且可以有效地以他们帖子末尾梦想的确切格式指定地图。这非常确实回答了这个问题。 +1
      • 这实际上是我正在寻找的,对于地图的误解,我的意思是我不想推回每个成员,因为我不知道这种语法。我接受另一个类似的答案,因为它更完整,但谢谢:)
      【解决方案5】:

      如果您只支持少量索引,从 0 到 50,则将函​​数指针放在数组而不是映射中会获得最佳性能。

      语法也很短:

      #include <iostream>
      #include <functional>
      
      static void f0() {
          std::cout << "f0\n";
      }
      
      static void f1() {
          std::cout << "f1\n";
      }
      
      void main()
      {
          std::function<void()> f[2] = { f0, f1 };
      
          f[0](); // prints "f0"
          f[1](); // prints "f1"
      }
      

      或者,如果你更喜欢类而不是函数:

      #include "stdafx.h"
      #include <iostream>
      
      class myfunc {
      public:
          virtual void run() abstract;
          virtual ~myfunc() {}
      };
      
      class f0 : public myfunc {
      public:
          virtual void run() {
              std::cout << "f0\n";
          }
      };
      
      class f1 : public myfunc {
      public:
          virtual void run() {
              std::cout << "f1\n";
          }
      };
      
      
      void main()
      {
          myfunc* f[2] = { new f0(), new f1() };
          f[0]->run(); // prints "f0"
          f[1]->run(); // prints "f1"
          for (int i = 0; i < sizeof(f) / sizeof(f[0]); ++i)
              delete f[i];
      }
      

      【讨论】:

        【解决方案6】:

        您似乎有两个问题:流控制问题(调度)和地图问题(实施说明)。我知道程序流在编译时是非静态且不可知的……但地图也是静态的吗?对于静态映射,我从使用 trait-ish 方法创建编译时映射中获得了很多好处。这是一个将文件后缀映射到 Objective-C 枚举常量的快速示例:

        namespace objc {
        
            namespace image {
        
                template <std::size_t N> inline
                constexpr std::size_t static_strlen(char const (&)[N]) { return N; }
        
                template <NSBitmapImageFileType t>
                struct suffix_t;
        
                #define DEFINE_SUFFIX(endstring, nstype)                                        \
                template <>                                                                     \
                struct suffix_t<nstype> {                                                       \
                    static constexpr std::size_t N = static_strlen(endstring);                  \
                    static constexpr char const str[N] = endstring;                             \
                    static constexpr NSBitmapImageFileType type = nstype;                       \
                };
        
                DEFINE_SUFFIX("tiff", NSTIFFFileType);
                DEFINE_SUFFIX("bmp",  NSBMPFileType);
                DEFINE_SUFFIX("gif",  NSGIFFileType);
                DEFINE_SUFFIX("jpg",  NSJPEGFileType);
                DEFINE_SUFFIX("png",  NSPNGFileType);
                DEFINE_SUFFIX("jp2",  NSJPEG2000FileType);
        
                template <NSBitmapImageFileType nstype>
                char const* suffix_value = suffix_t<nstype>::str;
        
            }
        }
        

        ……看看它是如何工作的?好的部分是使用它没有运行时开销,如果你的地图是静态的,你可以使用类似的东西。

        对于动态流控制和调度,函数指针起作用;如果你使用多态类和virtual 函数,这就是自动发生的事情,但看起来你已经有了一个架构,可能不适合用这种高度现代主义的架构概念重做。我喜欢 c++11 lambda,因为它们解决了我在这个领域中 90% 的问题。也许您可以详细说明(我会修改我的答案)!

        【讨论】:

          猜你喜欢
          • 2010-11-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-08-26
          相关资源
          最近更新 更多