我有一个树类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]
现在,我觉得两个拆分的类应该采用不同数量的参数(第二个参数要多一个!)。
如果您认为此问题没有建设性或广义的话,请告诉我,我将其删除。
既然没有其他建议,那么我应该从已经拥有的建议中选择哪个?最好的选择,就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_assert
s来使错误消息更好。
不过,您可以在此处使用继承来表达概念。通过使用模板,您仍然可以避免虚拟函数调用。例如:
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
final或just 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技术使它减少贪婪感。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句