我有一个树类Tree,我希望能够以不同的方式构建它。 build()函数将在Tree的构造函数中调用。
结果,在空间和数据结构方面将完全相同,但构建树的方式会有所不同(每个元素都将放置在哪个节点/叶子中)。节点和叶子的数量是事先知道的。
但是,build()有一个特定的原型。我希望用户只需查看interface
并了解他必须实施的内容。
所以,我想和template
一起去。在编写代码之后,我注意到Tree的用户没有任何接口来查看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]
build()的原型只需要std::vector
和一些size_t
个变量。在那个阶段,我只是在构建树,所以我没有任何关于如何在以后使用它的工作示例。
[EDIT.3]
最小的工作示例,它使用template
。此外,virtual
关键字也会发挥作用。
这将有效,但是用户可以实现自己的类,它不会从Base
继承并将类传递给它Calc
。我不希望他能够那样做。
Calc
是我在实际项目中的Tree
类,A
和B
分裂类。
#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;
}
[EDIT.4]
既然没有提出替代方案,那就是我应该从我已经拥有的方案中选择的方案?最好的选择,就OOP
“规则”而言。
我的目标不仅是做出设计决定,而且还要接受教育,其中一个方面是OOP
世界的方式。
[EDIT.5]
现在,我觉得两个分裂类应该采用不同数量的参数(再多一个参数!)。
如果您认为这个问题没有建设性或广泛性,请告诉我,我会将其删除。
答案 0 :(得分:1)
既然没有提出替代方案,那就是我应该从我已经拥有的方案中选择的方案?就OOP&#34;规则而言,最好的选择&#34;。
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
中的类型。然后,我们可以&#34;模板化&#34;只有构造函数而不是整个类。
然而,这个构造函数在第二个参数中是不受限制的:如果op(a)
无效,它可以采取任何操作,但编译失败。要限制Op
的类型,目前正在指定概念。希望它们能够作为ISO技术规范在今年(2014年)发布。
在我们获得它们之前,我们可以使用丑陋的SFINAE技术和static_assert
来更好地提供错误信息。
你可以在这里使用继承来表达一个概念。通过使用模板,您仍然可以避免虚拟函数调用。例如:
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;
};
我们可以轻松检查某个类是否来自其他类,这可以作为概念检查的简化版本。
但是,这个构造函数仍然是贪婪的:它会为第二个参数中的所有类型生成一个排序为Exact Match的重载。如果这成为一个问题,你必须使用SFINAE技术来减少它的贪婪。