0

I'm trying to overload the -> operator to eventually execute something along the lines:

MyInterface *myInstance = (MyInterface *)(new A());
myInstance->Toggle(); //this works wonderfully

std::shared_ptr<Wrapper<MyInterface>> sharedPtrWrapper = std::make_shared<Wrapper<MyInterface>>(myInstance);

//the next line doesn't compile, I would like to achieve something like this, but even 
//sharedPtrWrapper.get()->Toggle(); 
//would be nice to achieve, is this possible? 
sharedPtrWrapper->Toggle(); 

//this works:
sharedPtrWrapper->operator->()->Toggle();

Note: I have no control over MyInterface, cannot implement the pure virtual destructor.

Here is what I tried (the below code runs):

#import <memory>
#import <iostream>

struct MyInterface {
    virtual bool Toggle() = 0;
};

class A : public MyInterface {
public:
    bool Toggle() {
        stateEnabled = !stateEnabled;
        std::cout<<"current state " << stateEnabled << std::endl;
        return true;
    }
private:
    bool stateEnabled = false;
};

template <typename T>
class Wrapper {
private:
    T *unsafePointer = nullptr;
public:
    Wrapper<T>()
    { }

    T *operator->() const {
        return unsafePointer;
    }
    T *getInner() {
        return unsafePointer;
    }
    Wrapper<T>(T *stuff) {
        unsafePointer = stuff;
    }

    ~Wrapper<T>() {}
};

int main(int argc, const char * argv[]) {
    MyInterface *myInstance = (MyInterface *)(new A());
    myInstance->Toggle();


    Wrapper<MyInterface> wrapperS(myInstance);
    wrapperS->Toggle();


    std::shared_ptr<Wrapper<MyInterface>> sharedPtrWrapper = std::make_shared<Wrapper<MyInterface>>(myInstance);
    sharedPtrWrapper->operator->()->Toggle();
    sharedPtrWrapper.operator->()->operator->()->Toggle();

    sharedPtrWrapper.get()->operator->()->Toggle();

    (*sharedPtrWrapper).operator->()->Toggle();

    return 0;
}

Output:

current state 1
current state 0
current state 1
current state 0
current state 1
current state 0
Program ended with exit code: 0

To reiterate: This code doesn't compile:

sharedPtrWrapper->Toggle(); 

How to make it compile?

Edit : I'm using a wrapper because I have no control over the MyInterface, I get it from a library, also shared_ptr<MyInterface> mySharedPointer = std::make_shared<MyInterface>(myInstance); doesn't compile, because of the missing pure virtual destructor from the above mentioned interface.

Edit2: Example library usage in pseudocode:

void firstcallbackFromLib(Framework *framework) {
    MyInterface *myInstance = framework->getInstance();

    {
        Wrapper<MyInterface> wrapperS(myInstance);
        std::shared_ptr<Wrapper<MyInterface>> sharedPtrWrapper = std::make_shared<Wrapper<MyInterface>>(wrapperS);
        //assign sharedPtrWrapper and framework to static instances
    }
}
void myFunction() {
    sharedPtrWrapper->Toggle(); //this doesn't work, this is what i'm trying to achieve
    sharedPtrWrapper->operator->()->Toggle(); //this ugly thing works

}
void lastUninitCallbackFromLibrary() {
    MyInterface *p = sharedPtrWrapper.get()->getInner();
    framework->releaseInterface(p);
    //etc
}
StefanS
  • 1,089
  • 1
  • 11
  • 38
  • 4
    Note that if `A` implements the `MyInterface` interface, the cast in `(MyInterface *)(new A());` is not required. In fact, the cast is harmful because if `A` *isn't* a `MyInterface` then the cast will hide the problem with the illegal pointer assignment and cause problems to only show up at runtime making it harder to diagnose. – François Andrieux Feb 14 '20 at 14:38
  • Thank you @FrançoisAndrieux this was just an example, I'm getting the actual `MyInterface *myInstance` pointer from a library. But was not aware of this, thanks for pointing it out! – StefanS Feb 14 '20 at 14:43
  • Do you really need the wrapper class? There is no way to get `sharedPtrWrapper->Toggle();` to work if `sharedPtrWrapper` needs to be a `std::shared_ptr>` – NathanOliver Feb 14 '20 at 14:43
  • 1
    If you get the pointer from a library, then presumably the library is responsible for deleting it, right? Then instead of a wrapper you should be using a custom `std::shared_ptr` deleter that calls the responsible library function. If that is not the case, how would you know how to delete the pointer if it is to an abstract interface without virtual destructor? – walnut Feb 14 '20 at 14:44
  • @NathanOliver I explained it in my question: I have no control over the interface, and the interface doesn't contain a pure virtual destructor -> doesn't compile if i do something along the lines `shared_ptr mySharedPointer = std::make_shared(myInstance);` – StefanS Feb 14 '20 at 14:47
  • @walnut I'm trying to make it as generic as possible, I don't see a way of implementing a custom deleter that can achieve this. Any hints would be appreciated. – StefanS Feb 14 '20 at 14:48
  • 1
    @StefanS You're using it wrong. You need `shared_ptr mySharedPointer = std::make_shared();` – NathanOliver Feb 14 '20 at 14:48
  • 1
    @StefanS What does as generic as possible mean? There are two possibilities: Either you are responsible for cleaning up the pointer obtained from the library by calling a library function or `delete` (which makes no sense if the library returns a pointer to class without virtual destructor), in which case a custom deleter will do that and is 100% general. If you are not responsible for cleaning up the pointer, then there is no need for a `std::shared_ptr` in the first place, because it will be a non-owning pointer that should be passed as raw pointer. – walnut Feb 14 '20 at 14:50
  • 1
    If your library gives you a raw `MyInterface*` which points to a derived class and the `MyInterface` class doesn't have a virtual destructor, your library is simply broken. However, you can work around it by using `shared_ptr(static_cast(ptr))`. Consider adding a `assert(dynamic_cast(ptr))` before that though, to make sure you really have an `A` instance. – Ulrich Eckhardt Feb 14 '20 at 14:52
  • @walnut cool, thank you so much for that, consider posting it as an answer. Will try it out right now. – StefanS Feb 14 '20 at 14:53
  • 1
    @StefanS I suggest you post an example representing the library usage. I have no idea what exactly the library expects you to do. – walnut Feb 14 '20 at 14:54
  • @walnut, will do that, after I try implementing it myself, thank you! – StefanS Feb 14 '20 at 14:55
  • @walnut updated question with Edit2 – StefanS Feb 14 '20 at 15:17
  • 2
    @StefanS What is the return type of `framework->getInstance()`? Is it exactly `MyInterface*` or a pointer to a derived class? What type exactly does `framework->releaseInterface(p);` take as argument? `MyInterface*` exactly or a derived class? – walnut Feb 14 '20 at 15:45
  • @walnut exactly `void *`, takes as argument `void *` – StefanS Feb 14 '20 at 15:47
  • 1
    @StefanS And the library guarantees that this can be cast to `MyInterface*`? – walnut Feb 14 '20 at 15:51
  • @walnut yes. there are more arguments in the `getinterface(argument 1, argument 2)` that tells the framework to return the correct type. Then i cast it to `myinterface *`. I should have called it `NotMyInterface` haha – StefanS Feb 14 '20 at 15:52
  • 1
    @StefanS I am asking, because there is a difference between casting the `void*` to `ConcreteType*` and then `static_cast`ing it to `AbstractType*` and casting it directly to `AbstractType*`. One of these two has undefined behavior, depending on whether the pointer given by the library is referring to the `ConcreteType` or the `AbstractType`. – walnut Feb 14 '20 at 16:08
  • "I'm using a wrapper because I have no control over the MyInterface" -- that's a flawed interpretation, unfortunately. You didn't understand the problem and tried something. "`std::make_shared(myInstance)` doesn't compile, because of the missing pure virtual destructor" -- that's another misinterpretation. Sorry, but your question doesn't become clearer with all the edits and the discussion in the comments here. Don't make people piece together relevant info from various comments and edit sections. – Ulrich Eckhardt Feb 14 '20 at 16:52
  • @UlrichEckhardt my bad. I'm a beginner in c++. Will cleanup the question. I suppose this happened because I didn't know what information was relevant to you guys in advance. – StefanS Feb 14 '20 at 16:58
  • 1
    @StefanS What really is important is the documentation of the library function used here. Given that the library doesn't use the type system to document what it returns/accepts, but uses `void*` instead, you must rely on the documentation to know *exactly* to what the pointer points, otherwise you will have UB anyway. The question should have explained the library functions in sufficient detail and with signatures and then asked how to use a smart pointer to manage this library resource. – walnut Feb 14 '20 at 18:00

5 Answers5

3

I am confused about the question. Why wrapper class that does nothing?

If you want to put a class inside shared pointer yet do something uncommon at destruction: like, calling dll's function that performs the destruction, do some preprocessing, perform file closure instead of delete, or do nothing at all if that's what you want. Then you can simply specify it at shared pointer instantiation: https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr - see construction option 5.

ALX23z
  • 4,456
  • 1
  • 11
  • 18
  • 2
    This answer seems to me like it's seeking clarification rather than answering the question. It should probably be a comment instead. – François Andrieux Feb 14 '20 at 14:44
  • I explained it in my question: I have no control over the interface, and the interface doesn't contain a pure virtual destructor -> doesn't compile if i do something along the lines `shared_ptr mySharedPointer = std::make_shared(myInstance);` – StefanS Feb 14 '20 at 14:45
  • 2
    `make_shared` isn't supposed to work, that's why you do `new A`. That doesn't prevent the use of `shared_ptr` though. If you provide it with the concrete class (not a baseclass thereof) as constructor argument, it will invoke the correct destructor. It will do so even if you have a `share_ptr`! – Ulrich Eckhardt Feb 14 '20 at 14:48
  • 1
    @StefanS that's why I refer you to shared_ptr custom constructors. You can make shared_ptr with deleter that does nothing. See it in the link. – ALX23z Feb 14 '20 at 15:21
3

The problem is, that shared_ptr behaves like a pointer and Wrapper does that as well. In summary, you have code that behaves like a pointer to a pointer. In short, you could call (*sharedPtrWrapper)->Toggle(); instead of the abomination sharedPtrWrapper->operator->()->Toggle();.

Careful though: It's unclear what all this is supposed to achieve, because the example code is just an abstraction of your actual code. So, maybe it would just be more elegant to put a forwarding Toggle() method into class Wrapper, but that's impossible to tell with the info provided here.

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
2

You don't need your wrapper at all.

shared_ptr<MyInterface> mySharedPointer = std::make_shared<MyInterface>();

wont work because MyInterface is an abstract class. But, just like you can do

MyInterface *myInstance = new A();

To have a MyInterface * that points to a concrete derived object, you can use

std::shared_ptr<MyInterface> sharedPtr = std::make_shared<A>();

To get a std::shared_ptr<MyInterface> that points to a concrete derived object. You can then use sharedPtr to access Toggle like

sharedPtr->Toggle();

You can see that working in this live example

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Isn't this going to call the destructor from my A? I'm trying to avoid this. As A is constructed by a library, and i'm responsible to calling `somePointerFromLib->releaseInterface(myInstance);` After i'm done with that instance. – StefanS Feb 14 '20 at 15:03
  • 2
    @StefanS In that case use a custom deleter. There are plenty of examples on this site how to implement that like: https://stackoverflow.com/questions/45893205/wrapping-c-create-and-destroy-functions-using-a-smart-pointer – NathanOliver Feb 14 '20 at 15:07
1

sharedPtrWrapper->Toggle(); doesn't compile because of operator-> chaining rules explained well in this answer. In principle: if your object is NOT a pointer, operator-> is called recursively, if it is a pointer, member access is performed. Now std::shared_ptr has overloaded operator-> to access the raw Wrapper<MyInterface>* pointer kept inside and when it is applied on it, it tries to access Toggle, which does not exist.

For clarity note that this code also will not compile:

Wrapper<MyInterface>* wrapper = new Wrapper<MyInterface>(myInstance);
wrapper->Toggle();

You can do this however:

(*sharedPtrWrapper)->Toggle();

pptaszni
  • 5,591
  • 5
  • 27
  • 43
1

Use:

struct CleanupMyInterface {
  SomeTypeFromLib* somePointerFromLib = nullptr;
  void operator()( MyInterface* ptr ) const {
    if (somePointerFromLib && ptr)
      somePointerFromLib->releaseInterface(ptr);
  }
};
std::shared_ptr<MyInterface> sharedPtr( CreateAnInstanceOfAFromLibrary(), CleanupMyInterface{somePointerFromLib} );

shared_ptr has type-erased destruction, there is no need for a virtual destructor.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524