¿Usar plantillas para clonar tipos de clases exactamente?

I have a series of C++ classes that I wish to all be identical in functionality, but otherwise not related by inheritance. Effectively, these classes would differ in name only. (These classes will be thrown, and I do not want a catch clause for some base class to gobble up thrown derived objects. There seguirá be derived classes, but I wish to create discrete sets of thrown classes that are always segregated, as far as catch blocks are concerned.)

Of course, the downside to this is duplicating source code. I don't want to have to update N copies of the same code, whenever something needs to be changed.

I have already solved the code duplication problem via #define. But I think it would aid debug-ability if I could leverage templates, instead. The only thing parameterized in the template will be the class name itself.

I attempted the following, which did not work in gcc (w/ c++0x support enabled):

template<typename ClassName>
class ClassName
{
  public:
    ClassName(int foo, float bar) { ... }
   ~ClassName() { ... }

   bool SomePublicMethod() { ... }

  private:
    ...
}

Then I would declare the actual classes with something akin to:

typedef ClassName<UnrelatedClass1> UnrelatedClass1;
typedef ClassName<UnrelatedClass2> UnrelatedClass2;

I already know that the above does not work; I am providing it as a conceptual example of what I would like to accomplish, and am wondering if there is a way to make it work, other than the #define macro method that I am presently using (which suffers from diminished debug-ability.)

preguntado el 23 de septiembre de 13 a las 04:09

3 Respuestas

Use value specialized template:

template<int ID>
class ClassName
{
  public:
    ClassName(int foo, float bar) { ... }
   ~ClassName() { ... }

   bool SomePublicMethod() { ... }

  private:
    ...
}

typedef ClassName<1> UnrelatedClass1;
typedef ClassName<2> UnrelatedClass2;

Respondido el 23 de Septiembre de 13 a las 04:09

Or better still, use empty classes as tags: this way you don't need to worry about tag allocation or collisions, and could use meaningful names. template <typename Tag> class ClassName {...}; struct SomeTag{}; typedef ClassName<SomeTag> UnrelatedClass1; - Igor Tandetnik

@IgorTandetnik, Even better, typedef ClassName<struct UnrelatedClass1Tag> UnrelatedClass1; :) - Chris

@chris: Yes, I figured out what you are doing here a second after I sent that comment (since deleted). - Igor Tandetnik

@chris: I invite you to make that an answer, so that I can accept it (after I try it.) - Cognitive Hazard

@chris: You mean esta publicación, ¿quizás? - Xeo

This doesn't sound like a very good idea at all.

Exceptions classes should capture a specific type of error, with inheritance used to more generalise the type of error.

So for example you might have a 'disk crashed exception', which more generally might be a 'disk exception' and more generally and 'io exception' and always at its core an 'std::exception'

If all your exceptions are catching different types/classes of error, then why would they all have the same type of implementation.

Also it's uncommon to see #define mixed in the templates because that invariably makes it less readable when there is a compiler error (even if it seems more readable when it is no causing errors).

Perhaps you could provide more information about what is implemented in your exception classes, and I can see if I can help you further.

Respondido el 23 de Septiembre de 13 a las 04:09

1. The reason they are all the same is that I have reinvented my own specialized "std::exception". Mine is an abstract base class for all of the exception classes that I would clone with this template. 2. I am wanting to create categories of exceptions that don't mix. In particular: RVBAssertion (which will only be caught by main) and RVBException (which everything else would be derived from, i.e., IOException, DeviceException, etc., just as you describe.) Since... - Cognitive Hazard

...my abstract class can never be caught (because it cannot be instantiated,) and RVBAssertion and RVBException are type siblings, I won't ever have to worry about a catch block for RVBException gobbling up an RVBAssertion, or vice-versa. - Cognitive Hazard

An abstract class only needs one abstract member to be abstract (and uninsatiable), it can still provide functionality in other members. So your replacement for std::exception can have all the common code from RVBAEx and RVAAs but still be abstract. It is only interfaces that cannot have any implementation, and all prototypes must be pure/abstract. - Cuerdas

I do agree with others when they say that you should use inheritance. It is great for many purposes (one of them being the reason why you would like to have similar classes). It is not compulsory to write derived classes only when the objects are related - it is great even if just the functionality that is matching because your idea really is to put similar code together.

However, since your query was about creating multiple classes and we do not have enough view of your project, I believe it is possible that you may really need separate classes. One way of doing this is through macros. Here's a sample:

#include <iostream>
using  std::cout;
using  std::endl;

#define CUSTOM_CLASS(_CL) class _CL\
{\
      public:\
                  _CL(int foo, float bar) { cout << "Creating instance with foo=" << foo << ";bar=" << bar << endl;}\
       ~_CL() { }\
       bool SomePublicMethod() { cout << "Class created\n"; }\
};

CUSTOM_CLASS(myclass1);
CUSTOM_CLASS(myclass2);

int main()
{
    myclass1 instance1(1, 1.3f);
    myclass2 instance2(2, 0.3f);
    return 0;
}

If you run this using g++, you will get the following result:

Creating instance with foo=1;bar=1.3
Creating instance with foo=2;bar=0.3

Respondido el 23 de Septiembre de 13 a las 06:09

Please read my question again. I do have inheritance; I just have one single corner case where I don't want inheritance, but otherwise want identical code. Your proposed solution above uses #define, essentially identical to my own solution. My question here on SO was if the same thing could be accomplished with C++ templates. The answer I accepted above gives multiple options, all of which worked for me (but the comment by @chris was best, in my opinion.) - Cognitive Hazard

_CL is a reserved identifier. Also, this is rather ugly. Templates are the proper solution, see Iuri Covalisin's answer. - MSalters

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.