在类和函数中设计问题

时间:2014-05-09 14:28:56

标签: c++ oop templates c++11

我有一个树类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类,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;
}

[EDIT.4]

既然没有提出替代方案,那就是我应该从我已经拥有的方案中选择的方案?最好的选择,就OOP“规则”而言。

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

[EDIT.5]

现在,我觉得两个分裂类应该采用不同数量的参数(再多一个参数!)。

如果您认为这个问题没有建设性或广泛性,请告诉我,我会将其删除。

1 个答案:

答案 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技术来减少它的贪婪。