423,818 Members | 2,250 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 423,818 IT Pros & Developers. It's quick & easy.

C++ template class pointer polymorphism

P: 31
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:
Expand|Select|Wrap|Line Numbers
  1. #ifndef BASENONTEMPLATECLASS_H_INCLUDED
  2. #define BASENONTEMPLATECLASS_H_INCLUDED
  3.  
  4. class BaseNonTemplateClass{
  5. public:
  6.       virtual BaseNonTemplateClass();
  7.       virtual ~BaseNonTemplateClass();
  8. };
  9.  
  10. #endif // BASENONTEMPLATECLASS_H_INCLUDED
  11.  
And the template class:
Expand|Select|Wrap|Line Numbers
  1. #ifndef TEMPLATECLASS_H
  2. #define TEMPLATECLASS_H
  3.  
  4. #include "BaseNonTemplateClass.h"
  5.  
  6. template<class T>
  7. class templateClass : public BaseNonTemplateClass
  8. {
  9.     public:
  10.         templateClass();
  11.         virtual ~templateClass();
  12.         T* getData();
  13.  
  14.     protected:
  15.         T* data;
  16.  
  17.     private:
  18. };
  19.  
  20. template<class T>
  21. templateClass<T>::templateClass(){
  22.     data = new T();
  23. }
  24.  
  25. template<class T>
  26. T* templateClass<T>::getData(){
  27.     return data;
  28. }
  29.  
  30. #endif // TEMPLATECLASS_H
  31.  
Now I want to acess a instance of templateClass though a BaseNonTemplateClass pointer (because it is stored in a vector). How can I do this? I didn't get it compiling.
Apr 25 '18 #1
Share this Question
Share on Google+
18 Replies


weaknessforcats
Expert Mod 5K+
P: 9,196
OK I fixed the code so it would compile :
Expand|Select|Wrap|Line Numbers
  1. class BaseNonTemplateClass{
  2. public:
  3.     BaseNonTemplateClass();   //ctors and dtors are never virtual because they initialize the BaseNonTemplateClass object
  4.     ~BaseNonTemplateClass();
  5. };
  6.  
  7. BaseNonTemplateClass::BaseNonTemplateClass()
  8. {}
  9. BaseNonTemplateClass::~BaseNonTemplateClass()
  10. {}
  11.  
  12.  
  13. #ifndef TEMPLATECLASS_H
  14. #define TEMPLATECLASS_H
  15.  
  16. //#include "BaseNonTemplateClass.h"
  17.  
  18. template<class T>
  19. class templateClass : public BaseNonTemplateClass
  20. {
  21. public:
  22.     templateClass();
  23.     virtual ~templateClass();
  24.     T* getData();
  25.  
  26. protected:
  27.     T* data;
  28.  
  29. private:
  30. };
  31.  
  32. template<class T>
  33. templateClass<T>::templateClass()
  34. {
  35.     data = new T;
  36. }
  37. template<class T>
  38. templateClass<T>::~templateClass()
  39. {
  40.     delete data;
  41. }
  42.  
  43. template<class T>
  44. T* templateClass<T>::getData(){
  45.     return data;
  46. }
  47.  
  48. #endif // TEMPLATECLASS_H
  49.  
  50. int main()
  51. {
  52.     BaseNonTemplateClass* obj = new templateClass<int>;
  53. }
  54.  
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.
Apr 25 '18 #2

P: 31
Thanks! It worked (beside the commented out #include "BaseNonTemplateClass.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.
Apr 25 '18 #3

P: 31
Here are the important parts of my actual project, on which I have some similar structures, and I got a compiler error. InputBaseClass.h:
Expand|Select|Wrap|Line Numbers
  1. #ifndef INPUTBASECLASS_H
  2. #define INPUTBASECLASS_H
  3.  
  4. class InputBaseClass
  5. {
  6.     public:
  7.         InputBaseClass(){}
  8.         ~InputBaseClass(){}
  9. };
  10.  
  11. #endif // INPUTBASECLASS_H
  12.  
Then, InPort.h:
Expand|Select|Wrap|Line Numbers
  1. #ifndef INPORT_H
  2. #define INPORT_H
  3.  
  4. #include <deque>
  5. #include <memory>
  6.  
  7. #include "InputBaseClass.h"
  8. #include "OutputBaseClass.h"
  9.  
  10. template<class T>
  11. class InPort : public InputBaseClass
  12. {
  13.     public:
  14.         InPort();
  15.         virtual ~InPort();
  16.  
  17.         void registerOutputPort(std::shared_ptr<OutputBaseClass> output);
  18.         void finishAndDeleteLastBuffer();
  19.         T readLastBuffer(int index);
  20.         void requestData();
  21.  
  22.     protected:
  23.         std::shared_ptr<std::deque<std::unique_ptr<DataBlock<T>>>> outputData;
  24.         std::shared_ptr<OutputBaseClass> connectedOutput;
  25.  
  26.     private:
  27. };
  28.  
  29. template<class T>
  30. InPort<T>::InPort(){
  31.  
  32. }
  33.  
  34. template<class T>
  35. InPort<T>::~InPort(){
  36. }
  37.  
  38. template<class T>
  39. void InPort<T>::registerOutputPort(std::shared_ptr<OutputBaseClass> output){
  40.     outputData = output->getRegisterQueue();
  41.     connectedOutput = output;
  42. }
  43.  
  44. template<class T>
  45. void InPort<T>::finishAndDeleteLastBuffer(){
  46.     outputData->pop_back();
  47. }
  48.  
  49. template<class T>
  50. T InPort<T>::readLastBuffer(int index){
  51.     return outputData->back()->getData(index);
  52. }
  53.  
  54. template<class T>
  55. void InPort<T>::requestData(){
  56.     connectedOutput->requestData();
  57. }
  58.  
  59. #endif // INPORT_H
  60.  
Yeah, I know, many functions that propably don't make sense to you. Now, OutputBaseClass.h:
Expand|Select|Wrap|Line Numbers
  1. #ifndef OUTPUTBASECLASS_H
  2. #define OUTPUTBASECLASS_H
  3.  
  4. #include <memory>
  5. #include <deque>
  6.  
  7. #include "DataBlock.h"
  8.  
  9. class OutputBaseClass
  10. {
  11.     public:
  12.         OutputBaseClass(){}
  13.         ~OutputBaseClass(){}
  14. };
  15.  
  16. #endif // OUTPUTBASECLASS_H
  17.  
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 getRegisterQueue' 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?
Apr 25 '18 #4

weaknessforcats
Expert Mod 5K+
P: 9,196

P: 31
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)?
Apr 26 '18 #6

weaknessforcats
Expert Mod 5K+
P: 9,196
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:

Expand|Select|Wrap|Line Numbers
  1. Shape* s = new Triangle;
  2. Shape* s1 = new Circle;
  3.  
  4.  
Then this code:

Expand|Select|Wrap|Line Numbers
  1. void Function(Shape* arg)
  2. {  
  3. cout >> arg->Display();
  4. }
  5.  
displays the name of the shape:

Expand|Select|Wrap|Line Numbers
  1. Function(s);   //displays Triangle
  2. Function(s1);  //Displays Circle
  3.  
  4.  
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::Display(). 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::Display() 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?
Apr 27 '18 #7

P: 31
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?
Apr 27 '18 #8

P: 31
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).
Apr 27 '18 #9

weaknessforcats
Expert Mod 5K+
P: 9,196
OK. Let's try this:


Expand|Select|Wrap|Line Numbers
  1. #include <iostream>
  2. #include <vector>
  3.  
  4. using namespace std;
  5.  
  6.  
  7. //Set up typenames
  8.  
  9. enum MyTypes{INT = 1, FLOAT = 2 };
  10.  
  11. template <class T>
  12. class Data
  13. {
  14.     T data;
  15. };
  16.  
  17. class DataProxy
  18. {
  19.     void* obj;  //the address of the data object for this proxy
  20.  
  21.     MyTypes t; //the type used to create the Data object
  22.  
  23. public:
  24.     DataProxy(MyTypes arg);
  25.  
  26. };
  27.  
  28. DataProxy::DataProxy(MyTypes arg)
  29. {
  30.     t = arg;   //save the type so we can cast back to the correct type
  31.  
  32.     if (arg == INT)
  33.     {
  34.         obj = new Data<int>;
  35.     }
  36.     if (arg == FLOAT)
  37.     {
  38.         obj = new Data<float>;
  39.     }
  40. }
  41.  
  42. int main()
  43. {
  44.     vector<DataProxy> vec;
  45.     DataProxy obj1(INT);
  46.     vec.push_back(obj1);
  47.     DataProxy obj2(FLOAT);
  48.     vec.push_back(obj2);
  49. }
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.
Apr 28 '18 #10

P: 31
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)?
Apr 28 '18 #11

weaknessforcats
Expert Mod 5K+
P: 9,196
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.
Apr 28 '18 #12

P: 31
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_cast? I read that reinterpret is pretty unsafe, but if you are sure the cast will suceed normally it should be fast. Is that true?
Apr 29 '18 #13

weaknessforcats
Expert Mod 5K+
P: 9,196
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::operator int(). When called your object becomes an int. No cast needed:

DataProxy obj(INT);

int x = obj;

Might want to check this out.
Apr 29 '18 #14

P: 31
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.
Apr 30 '18 #15

weaknessforcats
Expert Mod 5K+
P: 9,196
Try this.

There is no casting. There is no template. The class is set up for int and float.
I convert input int to a string. I convert input float to a string.

When the user uses the object as int, it converts itself to an int. The same object will convert itself to a float if you use it as a float.

Let me know what you think.

Expand|Select|Wrap|Line Numbers
  1. #include <iostream>
  2. #include <vector>
  3. #include <sstream>
  4. #include <string.h>
  5.  
  6. using namespace std;
  7.  
  8. #define _CRT_SECURE_NO_WARNINGS
  9.  
  10. class GenericData
  11. {
  12.     string data;
  13.  
  14.  
  15.     string Convert(int arg);
  16.     string Convert(float arg);
  17.  
  18. public:
  19.     GenericData(int arg);
  20.     GenericData(float arg);
  21.     operator int();
  22.     operator float();
  23. };
  24.  
  25. GenericData::GenericData(int arg)
  26. {
  27.     data = this->Convert(arg);
  28. }
  29.  
  30.  
  31. GenericData::GenericData(float arg)
  32. {
  33.             data = this->Convert(arg);
  34. }
  35.  
  36. string    GenericData::Convert(int arg)
  37.     {
  38.         ostringstream str;
  39.         str << arg;
  40.         return str.str();
  41.     }
  42. string    GenericData::Convert(float arg)
  43. {
  44.     ostringstream str;
  45.     str << arg;
  46.     return str.str();
  47. }
  48.  
  49. //Conversion operators
  50.  
  51. GenericData::operator int()
  52. {
  53.     stringstream str(this->data);
  54.  
  55.     int temp = 0;
  56.  
  57.     str >> temp;
  58.  
  59.     return temp;
  60. }
  61.  
  62. GenericData::operator float()
  63. {
  64.     stringstream str(this->data);
  65.  
  66.     float temp = 0;
  67.  
  68.     str >> temp;
  69.  
  70.     return temp;
  71. }
  72.  
  73. int main()
  74.     GenericData obj1(5);     //an int
  75.     GenericData obj2(3.14159f);  //a double 
  76.  
  77.     int value (obj1);
  78.  
  79.     float valuef(obj2);
  80.  
  81.     cout << value << endl;
  82.  
  83.  
  84.  
  85.  
  86. }
May 1 '18 #16

P: 31
Ah, I wanted to do your approach with the proxy, but I have a problem. Your example itself is nice, but my problem still exists. I need to call functions on that template object (or, in this case, the proxy) which return data types differ, depending on the template argument of the object. How can I solve this using the proxy pattern? By the way, the return values aren't primitive data types such as int or float which you can convert easily, it's more like std::shared_ptr<std::deque<std::unique_ptr<DataBlo ck<T>>>> (:
May 4 '18 #17

weaknessforcats
Expert Mod 5K+
P: 9,196
The customary way to do that is to keep the data in the proxy object is a format that you can convert as needed to something else.

If your function has a 51423benam argument type all you need provide is an operator(51423bnam) member function. When the object is used to call a function with a 51423bnam argument type, the conversion operator will be implicitly called. The object data will be converted to 51423benam type an the call will proceed.

Your conversion operators are not limited to defined types.

Your example would need:

Expand|Select|Wrap|Line Numbers
  1. operator(std::shared_ptr<std::deque<std::unique_ptr<DataBlo ck<T>>>>)
  2. {
  3.   //convert to std::shared_ptr<std::deque<std::unique_ptr<DataBlo ck<T>>>>  here
  4. }
May 9 '18 #18

weaknessforcats
Expert Mod 5K+
P: 9,196
BTW: Sorry for the delay. I was out-of-state for a week at a seminar.
May 9 '18 #19

Post your reply

Sign in to post your reply or Sign up for a free account.