2014-05-09 44 views
0

我有一个树类树,我希望能够以不同的方式构建它。 build()函数将在Tree的构造函数中调用。课堂上的设计问题和功能

结果在空间和数据结构方面将完全相同,但构建树的方式会有所不同(每个元素将放置在哪个节点/叶中)。手前已知节点和叶子的数量。

但是,build()具有特定的原型。我希望用户只要看一下interface并知道他必须实施什么。

所以,我正在考虑去与template。在编写代码之后,我注意到树的用户没有任何接口来查看build()的原型。当然,我可以写在文档中或让他/她面对编译错误,但这不是一个好主意,恕我直言。

在这种情况下,用户会做:

Tree<SplitClass> tree; // ideal, if I could somehow notify him for the prototype 

于是,我想到了abstract类和(pure) virtual methods。再次,这工作,但现在用户必须做这样的事情:

Base* a = new SplitClass; 
Tree(a); 

在这里,我不喜欢,是用户所要做的使用new和用户可能不是那么好节目。而且,用户不能立即执行,例如template

最后,我尝试了一个function pointer,它可以工作,但不知道接口。

当然,还有一种解决方案是在其他文件中声明和定义分割函数并包含它们。

[编辑]

只是为了定义一个函数(该项目很重要的),应该创建一个类的事实,是一个坏主意?

[EDIT.2]

构建的原型()只需要std::vector和一些size_t变量。在那个阶段,我只是在构建这棵树,所以我没有任何有关它如何在以后使用的例子。

[EDIT.3]

最小工作实例,它使用了template。此外,关键字virtual也发挥作用。

这将起作用,但用户可能实现他自己的一个类,它不会从Base继承并通过类Calc。我不希望他能够做到这一点。

CalcTree这个类我在实际项目中有AB这个分裂类。

#include <iostream> 

template<class Operation> 
class Calc { 
public: 
    Calc(int arg) : 
     a(arg) { 
     Operation o(a, 10); 
     o.add(a); 
    } 

    void print() { 
     std::cout << a << "\n"; 
    } 

private: 
    int a; 
}; 

class Base { 
protected: 
    virtual void add(int& a) = 0; 
}; 

class A: public Base { 
public: 
    A(int& a, int c) { 
     a = a + c; 
    } 

    virtual void add(int& a) { 
     a += 100; 
    } 
}; 

class B: public Base { 
public: 
    B(int& a, int c) { 
     a = a - c; 
    } 

    virtual void add(int& a) { 
     a += 100000; 
    } 

}; 

int main() { 

    Calc<A> a(2); 
    a.print(); 

    Calc<B> b(2); 
    b.print(); 

    return 0; 
} 

[编辑。4]

因为,没有其他建议,这是我应该从我已经有的人应该遵循的吗?最好的选择,根据OOP“规则”。

我的目标不仅是做出设计决定,而且还要接受教育,其中一个方面就是要走在OOP世界。

[EDIT.5]

而现在,我觉得这两个分裂类,应该采取不同数量的参数(多了一个第二个!)。

如果您认为这个问题不具有建设性或广泛,让我知道,我会删除它。

+0

所以用户'实现或SplitClass'其他一些类/函数,然后把它传递给树的构造,以建立树?你能告诉我们更多的例子,以及这个类/函数是如何使用的? – dyp

+0

@dyp确切!我没有任何其他的小例子。我将编辑原型的样子。我正处于树木建设的阶段,所以现在只有创作才能发挥作用。我想要一个简单的方法来构建树(就像一个用'template'),但不知何故,我想构建()被强制为具有特定原型(如'virtual'关键字允许我们做的)并且用户能够通知构造函数应该使用哪个build()。感谢您的评论。 – gsamaras

+0

你可能寻找一个*概念*,这将有望在今年被指定为ISO技术规范(但他们不会是C++ 14标准本身的一部分)。 – dyp

回答

1

既然没有其他建议,那我应该从我已经拥有的那个中选择一个呢?就OOP“规则”而言,这是最好的选择。

C++是一种多范型编程语言,所以您不需要始终使用类层次结构和虚函数(幸运的是)。如果问题是分层的,或者可以用分层形式很好地描述,则使用OO。

为了专门研究C++中的算法,通常使用函数对象。函数对象是一个函数指针或类对象重载operator()

#include <iostream> 

class Calc { 
public: 
    template<class Op> 
    Calc(int arg, Op op) 
     : a(arg) { 
     op(a); 
    } 

    void print() { 
     std::cout << a << "\n"; 
    } 

private: 
    int a; 
}; 

如果函数对象是在构造函数中只用,你并不需要保存它,因此你不需要知道其类型Calc。然后,我们可以只对构造函数进行“模板化”,而不是对整个类进行“模板化”。

但是,这个构造函数的第二个参数是不受限制的:如果op(a)无效,它可以取任何东西,但不能编译。要限制Op的类型,概念当前正在指定中。希望它们将在今年(2014年)发布为ISO技术规范。

在我们拿到它们之前,我们可以使用丑陋的SFINAE技术和static_asserts来使错误信息更好。


可以在这里使用继承来表达一个概念,虽然。通过使用模板,您仍然可以避免虚函数调用。例如:

class MyInterface { 
protected: 
    virtual void add(int& a) = 0; 
}; 

class A final 
    : public MyInterface 
{ 
public: 
    A(int& a, int c) { 
     a = a + c; 
    } 

    virtual void add(int& a) final override { 
     a += 100; 
    } 
}; 

通过使A最后,或者只是add,编译器可以(可能)推断,这是虚拟函数的最终置换器,并避免调度。

class Calc { 
public: 
    template<class Op> 
    Calc(int arg, Op op) 
     : a(arg) { 
     static_assert(std::is_base_of<MyInterface, Op>(), 
         "Op must implement MyInterface"); 
     op(a); 
    } 

    void print() { 
     std::cout << a << "\n"; 
    } 

private: 
    int a; 
}; 

我们可以很容易地检查一类是从别的类派生而来,这可以作为一个概念检查的简化版本。

但是,这个构造函数仍然是贪婪的:它产生一个重载排列为第二个参数中所有类型的精确匹配。如果这成为一个问题,你将不得不使用SFINAE技术来减少贪婪。

+0

最后一件事。看来这个班级还需要一个参数。所以,我在考虑变量参数列表或者让两者都接受相同数量的参数,并且仅仅在一个类中(在类的函数中)评论额外的参数。我感觉到了这一点。列表会产生一些开销。你认为这是一个坏主意吗? – gsamaras

+0

@ G.Samaras如果你的意思是'va_args'等功能,你不应该使用它,如果你可以避免它。它有很多问题。你应该把所有参数从ctor传递给'Op',这可能会用在'Op'中。如果您选择抽象基类方法,这当然意味着所有派生类都将获得所有参数。如果其中一个派生类不需要所有参数,它可以忽略它们 - 例如不要在覆盖中给参数一个名字。如果您不使用ABC方法,则可以使用可变参数模板来接受其他参数。 – dyp