V
V
vipermagi2016-04-22 16:04:38
C++ / C#
vipermagi, 2016-04-22 16:04:38

What will the abstract class contain?

There is a seemingly simple learning task for C++: implement linked list, dynamic array, and static array classes. All three classes must inherit from the same abstract class.
It seems to be clear what a dynamic array can have in common, there is a pointer to the first element, an access method by index.
But the implementation of a static array generally somehow does not fit with the rest. To what extent does this part of the task correspond to common sense and real tasks? In my inexperienced opinion, there is already a regular built-in array and in the implementation of a dynamic array it should be used as a data field. I have no idea how to implement my own static array.

Answer the question

In order to leave comments, you need to log in

3 answer(s)
Z
Zelimkhan Beltoev, 2016-04-22
@vipermagi

In your case, the base class should have a pointer to the first element, a method to access by index, and a method to get the number of elements in the array. Perhaps, during the execution, highlight something else they have in common.
To implement a static array in the constructor, allocate memory for the required number of elements, and when accessing by index - just make an offset from the pointer to the first element - that's the implementation of the standard array

S
Sergey, 2016-04-22
Protko @Fesor

can you arrange the same interface for linked list and static array? You can. That's the whole answer.
In this case, the rest of the code will be tied to the abstract class that describes the interface, and it doesn’t matter which implementation you slip in. This is called subtype polymorphism.

M
Mercury13, 2016-04-22
@Mercury13

Option one you suggested. I write everything in C++03, without templates. C++11 and templates will of course give you more options.

class Object {
public:
    virtual ~Object() {}
};

class List {    // interface
protected:
    virtual Object& getAt(size_t i) = 0;
public:
    virtual size_t size() const = 0;
    inline Object& at(size_t i) { return getAt(i); }
    inline const Object& at(size_t i) const
        { return const_cast<List*>(this)->getAt(i); }
    virtual ~List() {}
};

Option 2. It’s not so easy for the user to use it, but if you add C ++ 11 lambda functions to it, it will be a bomb!
class ListCallback {    // interface
public:
    virtual void act(size_t index, Object& object) = 0;
    virtual ~ListCallback() {}
};

class ListConstCallback {   // interface
public:
    virtual void act(size_t index, const Object& object) = 0;
    virtual ~ListConstCallback() {}
};

class List2 {   // interface
public:
    virtual size_t size() const = 0;
    virtual void enumerate(ListCallback& body) = 0;
    virtual void enumerate(ListConstCallback& body) const = 0;
};

For example, for a dynamic array that has quick access by number, there will be such a body
void DynArray::enumerate(ListConstCallback& body) const {
   size_t sz = size();
   for (size_t i = 0; i < sz; ++i)
       body(i, operator[](i));
}

Of course, if you make the load not an abstract Object, but something final, dynamic_cast will not be needed.
Option 3 is accepted in Java. One drawback is that I write my code for const-correctness, so I'll omit it with your permission.
class VirtualListIterator { // interface
public:
    virtual bool next() = 0;
    virtual Object& value() = 0;
    virtual ~VirtualListIterator() {}
};

class ListIterator {   // Назван так для красоты, по сути это умный указатель
private:
    VirtualListIterator* ptr;
    // Запретим копирование и op=, но при желании можно реализовать и их
    ListIterator(ListIterator&) {}
    void operator=(const ListIterator&) {}
public:
    ListIterator() : ptr(NULL) {}
    ListIterator(VirtualListIterator* value) : ptr(value) {}
    ~ListIterator() { delete ptr; }
    bool next() { return ptr->next(); }
    Object* operator->() const { return &ptr->value(); }
    Object& operator*() const { return ptr->value(); }
};

class List3 {   // interface
public:
    virtual size_t size() const = 0;
    virtual VirtualListIterator* enumerate() = 0;
};

class Number : public Object {
public:
    int value;
};

// И пользоваться этим так...
void doSmth(List3& aList) {
    ListIterator it(aList.enumerate());
    while (it.next()) {
        std::cout << dynamic_cast<Number&>(*it).value << std::endl;
    }
}

In all three options, do not forget: if the load is not something final, but an abstract class, the elements will have to be stored by pointer and manually destroyed.
Yes, and you'll be a good engineer if you're thinking about performance right away.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question