Domanda

I have a tree class Tree and I want to be able to build it in different ways. The build() function will be called in the constructor of Tree.

The result, in terms of space and data structures will be exactly the same, but the way of building the tree will vary (where every element will be places, in which node/leaf). The number of nodes and leaves is known before hand.

However, the build(), has a specific prototype. I would like the user, to just look at an interface and know what he has to implement.

So, I was thinking of going with a template. After writing the code, I noticed that the user of the Tree doesn't have any interface to see the prototype of the build(). Of course, I can write it in the documentation or let him/her face the compilation error, but that's not a good idea, IMHO.

In this case, the user would do:

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

So, I thought about abstract classes and (pure) virtual methods. Again, this worked, but now the user has to do something like this:

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

Here, what I don't like, is that the user has to do use new and the users might not be so good in programming. Moreover, the user can not do it at once, like the template case.

Finally, I tried with a function pointer, which again will work, but no idea for the interface.

And of course, there is the solution of declaring and defining the splitting functions in other file(s) and include them.

[EDIT]

The fact that just to define one (very important for the project) function, one should create a class, is a bad idea?

[EDIT.2]

The prototype of build() takes only a std::vector and some size_t variables. In that phase, I am only building the tree, so I do not have any working example of how it will be used later.

[EDIT.3]

Minimal working example, which uses a template. Also, the virtual keyword comes into play.

This will work, but the user, might implement a class of his own, that will not inherit from Base and pass it the class Calc. I do not want him to be able to do that.

Calc is the class Tree I have in the actual project and A and B the splitting classes.

#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]

Since, there is no alternative proposed, which is the one I should follow from the ones I already have? The best option, in terms of OOP "rules".

My target is not only making the design decision, but also to get educated, in an aspect of which is the way to go in the OOP world.

[EDIT.5]

And now, I feel that the two splitting classes, should take different number of arguments (one more the second one!).

If you think, that this question is un-constructive or to broad, let me know and I will delete it.

È stato utile?

Soluzione

Since, there is no alternative proposed, which is the one I should follow from the ones I already have? The best option, in terms of OOP "rules".

C++ is a multi-paradigm programming language, so you don't need to use class hierarchies and virtual functions all the time (fortunately). Use OO if your problem is hierarchical, or if it can be described well in a hierarchical form.

To specialize algorithms in C++, you typically use function objects. A function object is a function pointer or class objects with an overloaded 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;
};

If the function object is only used in the constructor, you don't need to store it, hence you don't need to know its type in the class Calc. We then can "templatize" only the constructor instead of the whole class.

This constructor however is unrestricted in its second parameter: It can take anything but will fail to compile if op(a) is invalid. To restrict the types for Op, concepts are currently being specified. Hopefully, they'll be published in this year (2014) as an ISO Technical Specification.

Until we get them, we can use ugly SFINAE techniques and static_asserts to make the error messages better.


You can use inheritance here to express a concept, though. By using templates, you could still avoid virtual function calls. For example:

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;
    }
};

By making A final, or just add, the compiler can (could) infer that this is the final overrider of the virtual function, and avoid the dispatch.

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;
};

We can easily check if a class is derived from some other class, this can serve as a simplified version of a concept-check.

However, this constructor is still greedy: It produces an overload ranked as an Exact Match for all types in the second parameter. You'd have to use a SFINAE-technique to make it less greedy if that becomes a problem.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top