Interfaces and covariance problem

前端 未结 5 2036
我在风中等你
我在风中等你 2021-01-05 07:29

I have a particular class that stores a piece of data, which implements an interface:

template
class MyContainer : public Container

        
5条回答
  •  醉梦人生
    2021-01-05 08:18

    Like you said, the problem is that instances of Something are tied to the object it holds. So let's try to untie them.

    The key point to remember is that in OOP, public non-const data members are generally frowned upon. In your current implementation, every Something instance is tied to having a data member T x which is publicly accessible. Instead of this, is considered better to make an abstraction of this, i.e. provide accessor methods instead:

    class Something : IInterface
    {
    private:
        T x;
    
    public:
        T GetX()
        {
            return x;
        }
    };
    

    Now the user has know idea what type of thing x is, much less that x exists.

    This is a good first step, however, since you wish be able to have x refer to different objects at different times, we're pretty much going to have to make x be a pointer. And as a concession to conventional code, we'll also make GetX() return a const reference, rather than a regular value:

    class Something: IInterface
    {
    private:
        T *x;
    
    public:
        T const& GetX()
        {
            return *x;
        }
    };
    

    It's now trivial to implement the methods in IInterface:

    class Something: IInterface
    {
    private:
       T *x;
    
    public:
        T const& GetX()
        {
            return *x;
        }
    
        T& operator*()
        {
            return *x;
        }
    
        T* operator->()
        {
            return x;
        }
    
        Something& operator++()
        {
            ++x;
            return *this;
        }
    };
    

    The ++ operator is trivial now - it really just applies the ++ to x.

    The user now has no idea that a pointer was used. All they know is that their code works right. That's the most important point in OOP's principle of data abstraction.

    Edit

    As far as implementing the begin and end methods of Container, that shouldn't be too difficult either, but it will require some changes to Container.

    First off, let's add a private constructor to Something which takes a pointer to the starting object. We'll also make MyContainer a friend of Something:

    class Something: IInterface {

        friend class MyContainer; // Can't test the code right now - may need to be MyContainer or ::MyContainer or something.
    
    private:
       T *x;
    
        Something( T * first )
        : x(first)
        {
        }
    
    public:
    
        T const& GetX()
        {
            return *x;
        }
    
        T& operator*()
        {
            return *x;
        }
    
        T* operator->()
        {
            return x;
        }
    
        Something& operator++()
        {
            ++x;
            return *this;
        }
    };
    

    By making the constructor private, and setting the friend dependancy, we ensure that only MyContainer can make new Something iterators (this protects us iterating over random memory if something erroneous were given by a user).

    Next off, we'll change MyContainer a little, so that rather than having an array of Something, we'll just have an array of T:

    class MyContainer
    {
        ...
    private:
    
        T *data;
    
    };
    

    Before we get to implementing begin and end, let's make that change to Container I talked about:

    template
    class Container {
    public:
        ...
        // These prototype are the key. Notice the return type is IteratorType (value, not reference)
        virtual IteratorType begin() = 0;
        virtual IteratorType end() = 0;
    };
    

    So rather than relying on covariance (which would be really difficult in this case), we use a little template magic to do what we want.

    Of course, since Container now accepts another type parameter, we need a corresponding change to MyContainer; namely we need to provide Something as the type parameter to Container:

    template
    class MyContainer : Container
    ...
    

    And the begin/end methods are now easy:

    template
    MyContainer::begin()
    {
        return Something(data);
    }
    
    template
    MyContainer::end()
    {
        // this part depends on your implementation of MyContainer.
        // I'll just assume your have a length field in MyContainer.
        return Something(data + length);
    }
    

    So this is what I've got for my midnight thinking. Like I mentioned above, I cannot currently test this code, so you might have to tweak it a bit. Hopefully this does what you want.

提交回复
热议问题