C++ template class pointer polymorphism

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • 51423benam
    New Member
    • Apr 2018
    • 31

    C++ template class pointer polymorphism

    Hello!

    I have a template class which inherits from a non-template base class. It has functions the base class doesn't have (because the base class would need the template argument). But I don't know how to properly use polymorphism though a base class pointer in this case.
    Example:
    Code:
    #ifndef BASENONTEMPLATECLASS_H_INCLUDED
    #define BASENONTEMPLATECLASS_H_INCLUDED
    
    class BaseNonTemplateClass{
    public:
          virtual BaseNonTemplateClass();
          virtual ~BaseNonTemplateClass();
    };
    
    #endif // BASENONTEMPLATECLASS_H_INCLUDED
    And the template class:
    Code:
    #ifndef TEMPLATECLASS_H
    #define TEMPLATECLASS_H
    
    #include "BaseNonTemplateClass.h"
    
    template<class T>
    class templateClass : public BaseNonTemplateClass
    {
        public:
            templateClass();
            virtual ~templateClass();
            T* getData();
    
        protected:
            T* data;
    
        private:
    };
    
    template<class T>
    templateClass<T>::templateClass(){
        data = new T();
    }
    
    template<class T>
    T* templateClass<T>::getData(){
        return data;
    }
    
    #endif // TEMPLATECLASS_H
    Now I want to acess a instance of templateClass though a BaseNonTemplate Class pointer (because it is stored in a vector). How can I do this? I didn't get it compiling.
  • weaknessforcats
    Recognized Expert Expert
    • Mar 2007
    • 9214

    #2
    OK I fixed the code so it would compile :
    Code:
    class BaseNonTemplateClass{
    public:
    	BaseNonTemplateClass();   //ctors and dtors are never virtual because they initialize the BaseNonTemplateClass object
    	~BaseNonTemplateClass();
    };
    
    BaseNonTemplateClass::BaseNonTemplateClass()
    {}
    BaseNonTemplateClass::~BaseNonTemplateClass()
    {}
    
    
    #ifndef TEMPLATECLASS_H
    #define TEMPLATECLASS_H
    
    //#include "BaseNonTemplateClass.h"
    
    template<class T>
    class templateClass : public BaseNonTemplateClass
    {
    public:
    	templateClass();
    	virtual ~templateClass();
    	T* getData();
    
    protected:
    	T* data;
    
    private:
    };
    
    template<class T>
    templateClass<T>::templateClass()
    {
    	data = new T;
    }
    template<class T>
    templateClass<T>::~templateClass()
    {
    	delete data;
    }
    
    template<class T>
    T* templateClass<T>::getData(){
    	return data;
    }
    
    #endif // TEMPLATECLASS_H
    
    int main()
    {
    	BaseNonTemplateClass* obj = new templateClass<int>;
    }
    Be careful using virtual keyword. C++ had a long development cycle where one keyword after another was added. At one point the ANS committee said "NO MORE KEYWORDS". But C++ was not finished. So they reused existing keywords. Hence, virtual has many meanings depending upon how you use it.

    Post again. Let me know what happens.

    Comment

    • 51423benam
      New Member
      • Apr 2018
      • 31

      #3
      Thanks! It worked (beside the commented out #include "BaseNonTemplat eClass.h", but anyway). But actually I ran into another problem. The code I posted was a simplified test project, to simplify a bigger project with the same problem. I hoped I could just transfer the solution, but I don't know how to solve the main problem. I could post some code, but this is going to be more specific and more code.

      Comment

      • 51423benam
        New Member
        • Apr 2018
        • 31

        #4
        Here are the important parts of my actual project, on which I have some similar structures, and I got a compiler error. InputBaseClass. h:
        Code:
        #ifndef INPUTBASECLASS_H
        #define INPUTBASECLASS_H
        
        class InputBaseClass
        {
            public:
                InputBaseClass(){}
                ~InputBaseClass(){}
        };
        
        #endif // INPUTBASECLASS_H
        Then, InPort.h:
        Code:
        #ifndef INPORT_H
        #define INPORT_H
        
        #include <deque>
        #include <memory>
        
        #include "InputBaseClass.h"
        #include "OutputBaseClass.h"
        
        template<class T>
        class InPort : public InputBaseClass
        {
            public:
                InPort();
                virtual ~InPort();
        
                void registerOutputPort(std::shared_ptr<OutputBaseClass> output);
                void finishAndDeleteLastBuffer();
                T readLastBuffer(int index);
                void requestData();
        
            protected:
                std::shared_ptr<std::deque<std::unique_ptr<DataBlock<T>>>> outputData;
                std::shared_ptr<OutputBaseClass> connectedOutput;
        
            private:
        };
        
        template<class T>
        InPort<T>::InPort(){
        
        }
        
        template<class T>
        InPort<T>::~InPort(){
        }
        
        template<class T>
        void InPort<T>::registerOutputPort(std::shared_ptr<OutputBaseClass> output){
            outputData = output->getRegisterQueue();
            connectedOutput = output;
        }
        
        template<class T>
        void InPort<T>::finishAndDeleteLastBuffer(){
            outputData->pop_back();
        }
        
        template<class T>
        T InPort<T>::readLastBuffer(int index){
            return outputData->back()->getData(index);
        }
        
        template<class T>
        void InPort<T>::requestData(){
            connectedOutput->requestData();
        }
        
        #endif // INPORT_H
        Yeah, I know, many functions that propably don't make sense to you. Now, OutputBaseClass .h:
        Code:
        #ifndef OUTPUTBASECLASS_H
        #define OUTPUTBASECLASS_H
        
        #include <memory>
        #include <deque>
        
        #include "DataBlock.h"
        
        class OutputBaseClass
        {
            public:
                OutputBaseClass(){}
                ~OutputBaseClass(){}
        };
        
        #endif // OUTPUTBASECLASS_H
        And similar to InPort.h, there is another OutPort.h with specific functions, but they aren't important for this.
        Now my compiler says 'class OutputBaseClass has no member named getRegisterQueu e' in InPort.h in line 45. While that's true, that shouldn't throw an error, should it? Since the OutputBaseClass is only the base class and I acess it through a pointer, polymorphism should work, right? Any ideas?

        Comment

        • weaknessforcats
          Recognized Expert Expert
          • Mar 2007
          • 9214

          #5
          Time to read:


          Then post again.

          Comment

          • 51423benam
            New Member
            • Apr 2018
            • 31

            #6
            Can you please just say me what I am doing wrong? I read your article, and I read that visitor design pattern, however I don't completely understand it and feels a bit like overkill for my rather simple application, beside that I'm not sure how to implement it in my example (by the way, please be patient with me, I know I'm propably annoying you a bit, but I'm not experienced). So is there a way that this polymorphism just works (preferrably simpler that with the visitor design pattern)?

            Comment

            • weaknessforcats
              Recognized Expert Expert
              • Mar 2007
              • 9214

              #7
              Polymorphism has several flavors. The most common is IS-A. For example:

              A Triangle ISA Shape
              A Circe ISA Shape
              etc..

              The idea is to use Shape in the code instead of checking every time for a Circle or a Triangle before you do something. Not to mention what happens when Trapezoid gets added.

              This means the user creates a Triangle and uses it like a Shape:

              Code:
              Shape* s = new Triangle;
              Shape* s1 = new Circle;
              Then this code:

              Code:
              void Function(Shape* arg)
              {  
              cout >> arg->Display();
              }
              displays the name of the shape:

              Code:
              Function(s);   //displays Triangle
              Function(s1);  //Displays Circle
              So the function produces results based on the type of object passed to it. That means there is a display function which is called based on the objects actual type rather than the type of the pointer used in Function().

              To make this work you define Shape as a base class and derive the Triangle and Circle classes. The Triangle class has a Display() function Triangle::Displ ay(). Similarly Circle has Circle::Display ().

              Next you need a Display() function in the base class because you are using a base class pointer in that Function() call.

              Next you need to work it so that when you call Shape::Display( ) you actually call either Triangle::Displ ay() or Circle::Display (). You do this by defining Shape::Display( ) as a virtual function. This causes the compiler to add code to call the Display() function using the type created by the new operator rather than using the type in the Function() argument.

              So all functions you can call must be in the Shape class either as normal functions or as virtual functions. The Shape class is the "interface to the herarchy".

              BTW: Derived class destructors are not called when the Shape is deleted. All that's called is Shape::~Shape() . If derived classes use the new operator, those class destructors are not called and you have a memory leak.

              Therefore, you make the destructor in the base class virtual to cause the derived class destructor to be called before the base class destructor is called.

              Therefore, any class that has virtual functions and does not have a virtual destructor is a design error.

              The Visitor design pattern is to work around the restriction that the interface to the hierarchy must be in the base class.


              Do you understand all of this so far?

              Comment

              • 51423benam
                New Member
                • Apr 2018
                • 31

                #8
                Yes, I understand that part. And there is no workaround for the problem that every derived class function has to exist in the base class without using the visitor pattern?

                Comment

                • 51423benam
                  New Member
                  • Apr 2018
                  • 31

                  #9
                  I mean, since in my case I have the base class only because I want to store derived class templates of different types in a vector, I would be happy if there were another way to do this, since I would have to implement another class just to do this (beside the difficulty of the implementation) .

                  Comment

                  • weaknessforcats
                    Recognized Expert Expert
                    • Mar 2007
                    • 9214

                    #10
                    OK. Let's try this:


                    Code:
                    #include <iostream>
                    #include <vector>
                    
                    using namespace std;
                    
                    
                    //Set up typenames
                    
                    enum MyTypes{INT = 1, FLOAT = 2 };
                    
                    template <class T>
                    class Data
                    {
                    	T data;
                    };
                    
                    class DataProxy
                    {
                    	void* obj;  //the address of the data object for this proxy
                    
                    	MyTypes t; //the type used to create the Data object
                    
                    public:
                    	DataProxy(MyTypes arg);
                    
                    };
                    
                    DataProxy::DataProxy(MyTypes arg)
                    {
                    	t = arg;   //save the type so we can cast back to the correct type
                    
                    	if (arg == INT)
                    	{
                    		obj = new Data<int>;
                    	}
                    	if (arg == FLOAT)
                    	{
                    		obj = new Data<float>;
                    	}
                    }
                    
                    int main()
                    {
                    	vector<DataProxy> vec;
                    	DataProxy obj1(INT);
                    	vec.push_back(obj1);
                    	DataProxy obj2(FLOAT);
                    	vec.push_back(obj2);
                    }
                    This code uses a design pattern known as a proxy. Instead of a vector of base class pointers I use a vector of DataProxy objects.

                    The DataProxy object contains a pointer to the Data object that it is a proxy for plus an indicator for the type used to create the Data object.

                    In this version you create your Data object indirectly by creating a proxy instead.

                    Later you can use the proxy to fish out the pointer to the Data object and typecast that pointer to the correct type used to create the Data object in the first place.

                    The Mytypes is a way to avoid hard-coded types in the code. I set this up for just two types as an example.

                    Would this work for you?

                    If it will there is a thing called a VARIANT which can replace MyTypes. Google will get you the info on a VARIANT.

                    Let me know what you think.

                    Comment

                    • 51423benam
                      New Member
                      • Apr 2018
                      • 31

                      #11
                      Thanks for your reply (by the way, also for your patience)! I think, that pattern will be easy implementable for my project.
                      You mean for example boost::variant, right?
                      I only have one question left. I use some template classes for generic custom data containers. They are passed around in a realtime audio pipeline. As such, they should have a good performance. Do you think, this proxy pattern would have a big impact on the performance (especially when having templated functions which are called pretty often every second)?

                      Comment

                      • weaknessforcats
                        Recognized Expert Expert
                        • Mar 2007
                        • 9214

                        #12
                        There should not be a performance issue. However, that template object must travel with its type everywhere it goes. If you look at the VARIANT you will see the types are in a union. That is only one element in the union which will be as large as the largest type.

                        You would use the type discriminator to determine the type which tells you what type variable to use to read the data in the VARIANT.
                        Since there is only one variable in the union, picking the correct variable type is in effect a cast of the union contents. There is nearly zero overhead with as cast.

                        Keep posting until you are satisfied you have been sufficiently helped.

                        Comment

                        • 51423benam
                          New Member
                          • Apr 2018
                          • 31

                          #13
                          Ok now I don't know much about unions and variants, I think I just use the Proxy pattern in a way you posted above and then do casts whenever I want to call a non-baseclass function. I've calculated a little and figured out that shouldn't be a performance impact (audio sample rate of 44.100 hertz standard, buffer size 64 standard, makes only about 690 of this function calls per second). Can I just use static_cast for downcasting and then call the derived function? Or even reinterpret_cas t? I read that reinterpret is pretty unsafe, but if you are sure the cast will suceed normally it should be fast. Is that true?

                          Comment

                          • weaknessforcats
                            Recognized Expert Expert
                            • Mar 2007
                            • 9214

                            #14
                            There should be no performance problem using a cast. Today's processors can have maybe 8 virtual computers each of which is faster than last year's single processor chip.

                            There are several ways to proceed from here. I suggest you get it working and then, if you are so inclined, to make improvements.

                            For example, there is a C++ conversion operator you can write which the compiler will call for you. For example, you want to use your class object as an int. You would code DataProxy::oper ator int(). When called your object becomes an int. No cast needed:

                            DataProxy obj(INT);

                            int x = obj;

                            Might want to check this out.

                            Comment

                            • 51423benam
                              New Member
                              • Apr 2018
                              • 31

                              #15
                              Ok. Now I got another example working, now I'm trying it out on my main project. But do you think there is a way to integrate that proxy somehow in the base class including casting? Because another class adds not-much-doing-code.

                              Comment

                              Working...