【问题标题】:How to idiomatically call C++ functions based on variable value?如何根据变量值惯用地调用 C++ 函数?
【发布时间】:2010-08-05 20:01:46
【问题描述】:

假设我有一个数据类型enum TreeTypes { TallTree, ShortTree, MediumTree }

我必须根据一种特定的树类型初始化一些数据。

目前我已经写了这段代码:

int initialize(enum TreeTypes tree_type) {
    if (tree_type == TallTree) {
        init_tall_tree();
    }
    else if (tree_type == ShortTree) {
        init_short_tree();
    }
    else if (tree_type == MediumTree) {
        init_medium_tree();
    }
    return OK;
}

但这是某种愚蠢的代码重复。我没有使用任何强大的 C++ 功能,例如模板。

我怎样才能更好地编写这段代码?

谢谢,博达·赛多。

【问题讨论】:

  • 看起来改进你的代码应该从设计开始。您可以尝试通过巧妙地使用其他语言特性来替换像 if(){}else{} 这样好的旧控制结构,但我怀疑它会改进代码。写更多关于问题本身的内容。你想建模什么。
  • 我使用的代码比较复杂。我试图用非常小的例子来简化它。我正在处理一个非常大的项目,并且在一个地方用户可以拥有各种数据类型(这里我称它们为 TallTree、ShortTree、MediumTree)。对于每种数据类型,我都必须采取一些措施。就我而言,它有 12 种不同的类型,所以我的 if/else if/else if/ 是 12 种不同的 if/else if/... 语句。非常糟糕。
  • 但是当你实际调用初始化时,tree_type 参数是从哪里来的。是初始化普通函数还是某个类的方法?这些初始化函数是否作用于一些全局数据?他们初始化什么。没有参数。它们是方法吗?
  • 我也简化了。它们都采用相同的 3 个参数。 tree_type 参数来自用户输入,取决于用户选择的操作。一旦他选择它,我必须为他初始化对话框(这实际上是tree_type - 对话框类型),初始化函数使用指向窗口的指针来初始化对话框,指向用户信息和日志数据结构的指针(以前的操作保存在哪里,所以他可以撤消任务)。
  • 所以我在想的是一个像“initialize”这样的函数,我想在 tree_type 上模板化它,比如 initialize<tree_type>(window, user_info, logger) 并有单独的模板专业化,比如 template <> bool initialize<TallTree>(Window *w, UserInfo *u, Logger *l) 但这不太管用.我不能用变量tree_type 调用模板。

标签: c++ templates repeat


【解决方案1】:

您的代码可以处理两个或三个值,但您是对的,当您拥有数百个值时,您需要更强大的工业实力。两种可能的解决方案:

  • 使用类层次结构,而不是枚举 - 然后您可以使用虚函数并让编译器计算出要调用的实际函数

  • 创建一个枚举映射 -> 函数,您在启动时对其进行初始化 - 您的函数调用然后变成类似于 map[enum]->func()

模板在这里不能很好地工作,因为您试图在运行时做出决定,而模板在编译时完成它们的工作。

【讨论】:

  • 函数映射可以受益于使用boost::function/boost::bind()或TR1等效项。
  • 这听起来像一个 vtable,C++ 已经有了这个虚函数和多态......
  • @josh Golly,这不正是我的回答所说的吗?
  • @Neil 抱歉,误读;我想我的眼睛被 map[enum] 部分所吸引。
  • 如果有数百个值,第一个想法意味着数百个子类;这真的是个好主意吗?
【解决方案2】:

一句话:继承

class Tree { public: virtual void initialize() = 0; }

class ShortTree : public Tree {
public:
    virtual void initialize(){
        /* Short Tree specific code here */
    }
}

class MediumTree : public Tree {
public:
    virtual void initialize(){
        /* Medium Tree specific code here */
    }
}

class TallTree : public Tree {
public:
    virtual void initialize(){
        /* Tall Tree specific code here */
    }
}

然后,无论您要调用初始化,只要确保有一个指针或引用,多态性才能正常工作:

Vector<Tree*> trees;
trees.push_back(new SmallTree());
trees.push_back(new MediumTree();
trees.push_back(new TallTree();

// This will call the tree specific code for each tree in the vector
for(vector<Tree*>::iterator tree = trees.begin(); tree!=trees.end(); ++tree)
    tree->initialize();

【讨论】:

  • 你的基类最好有一个虚拟析构函数,假设你曾经对你有新的东西调用 delete。
  • @Neil Butterworth 我的代码已被占用并运行...我不再识别它了 :)
【解决方案3】:

使用由枚举值索引的查找表(假设所有函数具有相同的签名),即:

enum TreeTypes { TallTree, ShortTree, MediumTree, MaxTreeTypes }

typedef void (*p_init_func)(void); 

p_init_func initialize_funcs[MaxTreeTypes] =
{
    &init_tall_tree, 
    &init_short_tree,
    &init_medium_tree
};

int initialize(enum TreeTypes tree_type)
{ 
    initialize_funcs[tree_type]();
    return OK; 
} 

【讨论】:

    【解决方案4】:

    以及模板方式,因为您已经在标签中指出了它:

    enum TreeTypes { Tall, Short, Medium };
    
    struct TreeBase {
        // (...)
    };
    
    struct TallTree : public TreeBase {
        // (...)
    };
    
    struct ShortTree : public TreeBase {
        // (...)
    };
    
    struct MediumTree : public TreeBase {
        // (...)
    };
    
    template<TreeTypes N_type = Tall>
    struct Tree : public TallTree {
        // (...)
    };
    
    template<>
    struct Tree<Short> : public ShortTree {
        // (...)
    };
    
    template<>
    struct Tree<Medium> : public MediumTree {
        // (...)
    };
    

    这样你就可以为每个树类型获得单独的类,这些类可以通过基指针访问。将它们包装到 Tree 类中可以让您这样做:

    Tree<Tall> tall_tree;
    Tree<Short> short_tree;
    Tree<Medium> medium_tree;
    

    【讨论】:

      【解决方案5】:

      如果这个初始化真的是唯一的区别,那么我不确定任何其他成语会改善这种情况。

      您可以从 Tree 子类化并创建正确类型的树对象...但是您仍然需要区分要实例化哪个对象,因此您仍然会在某处得到类似的 if/else 块。

      也就是说,如果不仅仅是初始化不同,您应该继承并使用虚函数来实现它们之间的差异。

      【讨论】:

        【解决方案6】:

        试试 switch 语句:

        int initialize(enum TreeTypes tree_type) {
            switch (tree_type) {
                case TallTree: 
                    init_tall_tree();
                    break;
                case ShortTree:
                    init_short_tree();
                    break;
                case MediumTree:
                    init_medium_tree();
                    break;
            }
            return OK;
        }
        

        【讨论】:

          猜你喜欢
          • 2017-01-19
          • 1970-01-01
          • 1970-01-01
          • 2019-09-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多