0

My apologies in advance if I'm not asking this in a right Stack Overflow forum. I'm trying to come up with a design which is easily expandable and also easy to use. So here is my problem:

In C++, I have two classes, called Class One and Class Two which both supports an operation called doSomething(T& data). T is a struct which for example has two members for Class One and has four members for Class Two. Since I want to enforce doSomething(T& data) into those classes, I have a base class called Base.

template <class T>
class Base
{
    typedef T    data_type;
    virtual bool doSomething(data_type& data) const = 0;
};

struct OneData { int a; int b; };
class One : public Base<OneData>
{
    bool doSomething(data_type& data) const { ... }
};

struct TwoData { int a; float b; int c; char d; };
class Two : public Base<TwoData>
{
    bool doSomething(data_type& data) const { ... }
};

One big problem I have with this approach is, I cannot defined a Base type variable. I'm forced to use One or Two as type which forces me to defined 2 variables (what I really want is to create Base type variable and allocate memory or initialize it to One or Two based on the given T).

Furthermore, I was thinking to create class called OneTwo which defines doSomething(T& data) which basically hides the headache of which object to create from clients.

// This is just idea of what I want but don't know how??
template<class OneT, class TwoT>
struct OneTwo
{
    bool doSomething(T& data) const // Here I don't know how to get to T
    {
        if (OneT::owns(data))      // call OneT::doSomething
        else if (TwoT::owns(data)) // call TwoT::doSomething
        else                       // Maybe exception, error, ...
    }
}; 

I'm not sure even if I did right approach on class One and Two. Also, I tried to explain it as clear as possible but please ask if anyone need more information.

Please help me to understand on how to design this?

Thank you,


UPDATE:

In nutshell, I want to have minimum of 2 classes (class One and Two in above example) and possibly more that follow same interface such implement same operation (doSomething(...) in above example), but the given data to that operation is different for each class.

Moreover, I want to provide a single interface to all those classes classes (class OneTwo in above example) for ease of use.

Orion
  • 544
  • 5
  • 15
  • Whatever `T` is, `One` and `Two` should be subclasses of ***that***. – Sam Varshavchik Jun 14 '17 at 00:50
  • 3
    What's the ultimate goal of the exercise? Imagine that you managed to implement your ideal, perfect design - what would the calling code look like? How do you plan to use this contraption? Your description makes no sense to me, no offense intended. It looks like an [XY problem](http://xyproblem.info/) – Igor Tandetnik Jun 14 '17 at 01:09
  • You cannot instantiate `Base` since it is pure virtual, that is, it has a `virtual` function assigned to `0` – snoopy Jun 14 '17 at 01:09
  • @snoopy I'm not trying to initialize the `Base`, what I want is for example to have `Base* ptr` and then use `new One or Two` based on the `T` – Orion Jun 14 '17 at 01:13
  • If you are looking at instantiating depending on the template argument type, you could try using `decltype` (C++11 onwards) - http://en.cppreference.com/w/cpp/language/decltype – snoopy Jun 14 '17 at 01:17
  • @Igor Tandetnik, the whole point is for me to understand and learn to design in C++. None taken, please let me know which part you don't understand and I'll explain it to you. – Orion Jun 14 '17 at 01:18
  • Then have your `Base` template inherit from a single class – Passer By Jun 14 '17 at 01:18
  • @SamVarshavchik `T` is a different `struct` for `One` and `Two`. If I do that, I break polymorphism. Maybe I don't see the big picture and please explain more if possible. – Orion Jun 14 '17 at 01:21
  • @PasserBy Inheriting from empty `struct`? – Orion Jun 14 '17 at 01:22
  • 5
    Suppose you managed, by some magic, to declare `Base* ptr` and assign either `One*` or `Two*` to it. What next? I imagine you'd then want to call `ptr->doSomething(...)` - but what would you pass as an argument? `One::doSomething` and `Two::doSomething` take different parameter types. How is the calling code, with only `Base* ptr` in hand, going to figure out which type to use? You keep talking about two classes that implement "the same interface" - but your `One` and `Two ` do **not**, in fact, implement the same interface. – Igor Tandetnik Jun 14 '17 at 01:38
  • @IgorTandetnik I see your point. I updated my question. Not sure if you looked at it. Please let me know if the updated version make any sense and if so how would you design it – Orion Jun 14 '17 at 01:49
  • 3
    Yes, I've seen your update. No, it still doesn't make sense. You are laboring under belief that `One` and `Two` "follow same interface" and "implement same operation" - but they do no such thing, no more than `class Artist { void draw(Painting); };` and `class Cowboy { void draw(Gun); };` do. Having a method with the same name is necessary but not sufficient. – Igor Tandetnik Jun 14 '17 at 02:01
  • @IgorTandetnik I see your point. Thanks for explaining it. I will go ahead and rethink the problem. Thanks again for clarifying it. – Orion Jun 14 '17 at 02:13
  • @IgorTandetnik - Kudos on that excellent analogy! – Omnifarious Jun 14 '17 at 02:16
  • The title needs to be changed, but I don't know what to. :-/ Design Issue (C++) is way too generic. – Omnifarious Jun 14 '17 at 02:18

1 Answers1

2

Here is a design that comes close to doing what you've asked to do, and hopefully when you see it, you'll understand that what you are asking for is logically inconsistent, like asking for a circle in euclidean space in which the ratio between the radius and the diameter is 3.

#include <stdexcept>

struct OneData;
struct TwoData;

class Base {
  public:
   virtual bool doSomething(OneData &) const { throw ::std::runtime_error("I don't know how to operate on a OneData!"); }
   virtual bool doSomething(TwoData &) const { throw ::std::runtime_error("I don't know how to operate on a TwoData!"); }

   virtual ~Base() = 0 { } // Make this an abstract base class that can't be instantiated.
};

struct OneData { int a; int b; };
class One : public Base
{
    virtual bool doSomething(OneData& data) const { return; }
};

struct TwoData { int a; float b; int c; char d; };
class Two : public Base
{
    virtual bool doSomething(TwoData& data) const { return; }
};

You can now have your Base * and call doSomething on it to your heart's content giving it a OneData or a TwoData as you please. But woe betide you if you give it the wrong type for the class it actually is because it'll throw an exception.

Now, how do you fix that? Well, you can't. doSomething for a One class must have a OneData and for a Two class must have a TwoData but you don't know which you have if you have a Base *.

Perhaps you can instead ask a different question. Because the train of thought that led you to ask this nonsense question that has no answer left somewhere. And that's where it needs to be put back on track. What led you to believe you wanted to do this thing that makes no sense?

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • 1
    Thanks for you answer. I see your point and IgorTandetnik point. I will get back to my drawing board and rethink this problem in different way. Thanks again! – Orion Jun 14 '17 at 02:12
  • 1
    Making the virtual functions in the base class protected would give you compile-time protection, It would effectively prohibit calling the functions using a base class pointer without first doing a dynamic_cast, this would force the user to check before the call that he's dealing with the right kind of object, thus limiting the possibility of hitting a run-time error. Relying on exceptions for type checking can slow down execution quite a bit. – Michaël Roy Jun 14 '17 at 04:51
  • @MichaëlRoy well, it wasn't meant to ever be used. It was more a demonstration of why the idea was flawed. – Omnifarious Jun 14 '17 at 04:55
  • I know. :) Fact is, I can't remember what I changed to my coding style to avoid this kind of problem. All I know is this kind of issue doesn't present itself anymore. – Michaël Roy Jun 14 '17 at 05:02
  • @Omnifarious, you can make `doSomething` pure virtual, and the destructor `= default;` for a cleaner interface. (https://stackoverflow.com/a/5482025/2523211) – ZivS Jun 14 '17 at 10:12
  • @ZivS - I indeed could. But my goal was to try to make it blindingly obvious what the problem/contradiction was. And having two methods in the base that were each implemented in both derived classes puts the problem at a remove. Implementing them and throwing an exception was kind of like putting the exclamation point at the end of a sentence. The goal of a well written program is to communicate intent, and in this case my intent wasn't a working, well-designed program, it was a statement as to why this was an unworkable idea. – Omnifarious Jun 14 '17 at 14:53