3

It might be best to skip to the code and read the comments for a quick introduction to what I'm trying to do, however there are more vital details I have to try to describe:

I want to add a member function to a class which is inherited through the class heirarchy. I want to make it so that derived classes don't have to retype any code or do anything special to automatically get this function, and I would like to avoid macros. This function should also be inherited by any subclasses made after compilation (this is part of a platform that other people will extend).

The function is a debugging function, which keeps track of the customised smart pointers (much like std::shared_ptr) that point to instances of the class - I want to be able to find out why a given instance is being held in memory, which means I need to know what pointers are pointing to it. The customised smart pointers are templated, and I want to enforce that the specialisation of the smart pointer used as the argument matches the type of the instance's static type.

Since this is in a framework, it is reasonably important to enable this function for people extending the hierarchy after compile.

This is being compiled on Windows with Visual Studio 2010. We're moving to VS 2015 soon, but I'd prefer not to wait. We're also mostly cross platform (we turn a couple of things off for other platforms, only when necessary). If there is a solution that would require C++11/14/17/later, I'd still like to hear it out of interest.

I feel like I need to make the function templated and specialised at the same time, specialised with some sort of 'Self', but I have no idea how to make subclasses get what is essentially copy-pasted, rather than inherited code. I also understand (I think) that this function is not appropriate to make virtual, but I also want to prevent subclasses from getting the parent classes' specialisation.

I think there are a few options I can go for, but I'd like to know if there's something better that I haven't thought of:

  • Just give up on trying to achieve this kind of safety
  • Use dynamic checking instead - I haven't worked out how to do this yet. I think it would be easier, but less safe, and I like safety.
  • Use a macro to automatically make a typdef to Self, ala https://stackoverflow.com/a/21149648/78823
    • I'm not sure exactly how this works. Either way, it's only half the problem, and I don't think it gets me any closer to solving the function declaration / inherit-but-not-really puzzle
    • Also not a fan of forcing people to use a macro, especially for something that's only for debugging.

I'm fully expecting that a completely different design pattern would be the best solution, but such a solution hasn't occurred to me yet.

I really think the compiler would be able to know everything I need it to do support this, but I'm not sure the language does.

I would also very much appreciate some help or feedback about how to better word this question. I'm having a hard time getting my head around the problem myself, let alone wording it for other people to understand.

template<class T>
class CustomSmartPointer {};

class Base {
public:
    void doSomething(CustomSmartPointer<Base> arg) {} //Should be able to say something like CustomSmartPointer<Self> instead, where the compiler knows what I mean by Self
};

class Derived : public Base {
public:
    void doSomething(CustomSmartPointer<Derived> arg) {} //Shouldn't have to specify this declaration, or code, again, as it is direcly copy-pastable from above
};

class Derived2 : public Base {};

void main() {
    Base b;
    Derived d;
    Derived2 d2;

    CustomSmartPointer<Base> cb;
    CustomSmartPointer<Derived> cd;

    b.doSomething(cb);

    d.doSomething(cd);

    d2.doSomething(cb); //This shouldn't compile, as cb is the wrong type for the specialisation that should exist for Derived2::doSomething
}
Community
  • 1
  • 1
Lynden Shields
  • 1,062
  • 1
  • 10
  • 27
  • Also, if anyone suggests that we should switch to std::shared_ptrs, I think I would still have exactly the same issue. – Lynden Shields May 28 '15 at 12:22
  • 2
    Note that `void doSomething(CustomSmartPointer arg)` and `void doSomething(CustomSmartPointer arg)` are unrelated member functions (although, they are overloads of the same name inside `Derived`). If you don't want `void doSomething(CustomSmartPointer arg)` to be available in `Derived`, then I don't think inheritance is appropriate in your case. – eerorika May 28 '15 at 12:33
  • Yeah I realise that. It's something very similar to inheritance though, so I don't know what to call it. – Lynden Shields May 28 '15 at 12:34
  • On a side note, `main` must return an `int`. – eerorika May 28 '15 at 13:36
  • Not according to my copy of Visual Studio :P - but I'm being sloppy because it's irrelevant to my question. – Lynden Shields May 28 '15 at 13:41

3 Answers3

2

I'll repeat my comment which explains why runtime inheritance doesn't fit: void doSomething(CustomSmartPointer<Base> arg) and void doSomething(CustomSmartPointer<Derived> arg) are unrelated member functions (although, they are overloads of the same name inside Derived). If you don't want void doSomething(CustomSmartPointer<Base> arg) to be available in Derived, then runtime inheritance is not appropriate in your case.

CRTP is probably what you're looking for.

template <class T> 
struct Base {
    void doSomething(CustomSmartPointer<T>);
};

struct Derived: Base<Derived> {}; // will inherit void doSomething(CustomSmartPointer<Derived>) from parent
struct Derived2: Base<Derived2> {}; // will inherit void doSomething(CustomSmartPointer<Derived2>) from parent

Or course, this means that derived classes won't have a common parent. You can give Base a non-template parent to fix that, but you cannot use that as an interface for doSomething.

On the other hand, if you cannot modify Base, and Derived must inherit Base then there is no way to avoid the member function being inherited and if it is a bug to call it, it would be tricky to catch the bug compile time. You can however override doSomething(CustomSmartPointer<Base>) in Derived and throw a runtime exception to catch the bug runtime.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • This looks very promising. Haven't come across CRTP before – Lynden Shields May 28 '15 at 12:47
  • Our inheritance heirarchy exists outside of this problem, `Derived` must subclass `Base`. This means that `doSomething(CustomSmartPointer )` will be inherited by derived still. Is there something obvious I'm missing to get around this? – Lynden Shields May 28 '15 at 13:00
  • Is `Base::doSomething` depended upon by something? i.e. you can't get rid of it. That would be a problem for this. – eerorika May 28 '15 at 13:18
  • `Base::doSomething` is required so that I can track CustomSmartPointers that point to instances of `Base`. As in, `doSomething` really should be called `registerASmartPointerToMe(CustomSmartPointer cspInstance)` or similar – Lynden Shields May 28 '15 at 13:21
  • This particular issue might disappear if we didn't subclass concrete classes, but shifting to a pattern that doesn't would be significant work we're not in a position to do yet, AFAIK. – Lynden Shields May 28 '15 at 13:22
  • Well, if `Derived` must inherit `Base` and `Base` must have `doSomething(CustomSmartPointer)` then there is no way to get rid of it in `Derived`. Best you can do is override it and throw a runtime exception. – eerorika May 28 '15 at 13:26
  • I'd be happy to have runtime exceptions if I need to, but the inheritance makes calling `doSomething` ambiguous. – Lynden Shields May 28 '15 at 13:30
  • Oooh, I think I'm really getting there! I can fully qualify the call, which is OK as it's in debugging code anyway, i.e.: `d.Debuggable::doSomething(cd);` – Lynden Shields May 28 '15 at 13:37
  • I'll have to check this out tomorrow when I'm back at work, to try it in the exact situation it is in at work, but it's working in my minimal example. – Lynden Shields May 28 '15 at 13:39
  • I can even use `d.Debuggable::doSomething(cd);` (without ``). I don't understand why this isn't also ambiguous, but it seems to work, checking the RTTI inside the implementation of doSomething shows the correct info and using the wrong pointer type won't compile. Thanks a lot. As I said, will double check against actual situation when I can and report back. – Lynden Shields May 28 '15 at 14:03
  • This solution is really compelling, but I'm not sure it's appropriate for us at this time, as derived classes will still need to declare that they're deriving from the CRTP class. Introducing consumers of the framework to CRTP and multiple-inheritance just to support some debugging tools that are likely to be turned off just don't seem worth it at the moment. I could make this simpler with a macro they would use instead of the `class Dervived: public Base, public Debuggable`, but same story. I will accept this answer, but would appreciate a further suggestion to get around this – Lynden Shields May 29 '15 at 01:12
1

A possible way to achieve this could be CRTP, although it might look ugly and complicate inheritance:

template<class T>
class CustomSmartPointer {};

template <class D>
class Base {
public:
    void doSomething(CustomSmartPointer<D> arg) {} 
};

class Derived : public Base<Derived> {};

class Derived2 : public Base<Derived2> {};

int main() {
    Derived d;
    Derived2 d2;

    CustomSmartPointer<Derived> cd;

    d.doSomething(cd);

    d2.doSomething(cd); //does not compile
    return 0;
}
m.s.
  • 16,063
  • 7
  • 53
  • 88
  • I need to have the inheritance between Derived and Base as it is an existing, well established inheritance. If I re-establish that inheritance, then doSomething becomes ambiguous. – Lynden Shields May 28 '15 at 13:12
  • @LyndenShields do you mean you cannot change `Base`? – m.s. May 28 '15 at 13:16
  • Not that much. I think what I mean is that I can't make `Base` templated, but I can make a base class of `Base` that is templated if that helps at all. – Lynden Shields May 28 '15 at 13:19
  • Thanks, you and @user2079303 have given very similar answers, and with some more back and forth, I think we've come up with a solution. Will accept when I confirm in the office. – Lynden Shields May 28 '15 at 13:43
0

What happens when you complile a c++ file with templates is that the compiler creates a specialised template function when it is needed. (That is the instantiated from here in your errors). in your case the compiler will create two classes: CustomSmartPointerBase and CustomSmartPointerDerived. Now you make a function in base: doSomething(CustomSmartPointerBase). This function is also available in the Derived class, because it is a subclass. The doSomething declaration in Derived does not override the one from Base because it takes a different type as argument, so it is overloaded.

This means that the Derived class has two functions in it: a doSomething for either kind of CustomSmartPointer.

You can try this out by adding cout << "doSomethingBase" << endl; and cout << "doSomethingDerived" <<endl; statements in the respective doSomething fuctions and running the code. Or better, step through it with your debugger.

David van rijn
  • 2,108
  • 9
  • 21
  • Yeah, this is something I don't want, as explained in my comment on the line with `d2.doSomething(cb);` I want that to not compile. – Lynden Shields May 28 '15 at 12:43
  • Haha Stupid code that keeps compiling ;) But, what you're asking is if there is a way to inherit a class and to make sure the inherited methods are made invisible. You could do that via private inheritance: `class Derived : private Base` . But that hides all the inherited methods, but to my knowledge there is not really a clean way to do this. – David van rijn May 28 '15 at 12:51
  • PS. i think you could also add `private: void doSomething(CustomSmartPointer arg) {}` in the derived class. But this kind of stuff goes against the nature of OO programming, because the nature of a subclass is that it does the same as the superclass, and more. – David van rijn May 28 '15 at 12:54
  • Yeah, the ability to hide the functions would be good, but I don't want to force Derivers to remember to do this, which goes with your comment about OO. I'd be happy to find a solution that isn't part of the class itself to satisfy OO principles, but I don't know how to go about that either. – Lynden Shields May 28 '15 at 13:24
  • 1
    I don't know about your real world application, but to me @user2079303 's solution looks pretty reasonable. – David van rijn May 28 '15 at 13:33