C++: Creating a templated Shared object rather

2019-07-18 23:36发布

问题:

Per my previous question, I wish that a boost::shared_ptr<A> was actually a subclass of A (or perhaps A*) so that it could be used in methods that took A* as their argument.

Consider the following class:

class A
{
public:
    A(int x) {mX = x;}
    virtual void setX(int x) {mX = x;}
    virtual int getX() const {return mX;}
private:
    int mX;
};

In the previous question, I proposed the creation of a SharedA object to take care of this, and presumably it does.

class SharedA : public A
{
public:
    SharedA(A* a) : mImpl(a){}
    virtual void setX(int x) {mImpl->setX(x);}
    virtual int getX() const {return mImpl->getX();}
private:
    boost::shared_ptr<A> mImpl;
};

It would be Grrrrrrrreat thought, if I could create a template class to take care of all of this for me.

template <class T>
class Shared : public T
{
public:
    SharedT(T* t) : mImpl(t)
    {
    //What kind of crazy voodoo goes here?
    }

private:
    boost::shared_ptr<T> mImpl;
};

If I had this, (along with the proper constructors in Shared<T>), then I could do the following:

A* indestructo_A = new Shared<A>(new A(100));
A* indestructo_A_clone = new Shared<A>(indestructo_A);
delete indestructo_A
cout << "Indestructo-A back with a vengence!" << indestructo_A_clone.getX();

Questions:

  1. Is this useful? Or is its utility only in corner cases where you're dealing with particularly bad code. For instance:

    void aFunctionYouHaveToUse(A* a) { /some useful algorithm and then/
    delete a; }

  2. Is it possible to build such a templated class? (I guess you need reflection, right?) If you can build it, how?

回答1:

There's a really, darn good reason why shared_ptr does not allow explicit casting to A* (there are better ways of doing it than inheriting, which would be impossible anyway). The whole purpose of shared_ptr, and other smart pointers, are to provide a small, encapsulated object that's sole purpose is to own a pointer and to decide when and how to delete it.

If this object allowed that same pointer to just get passed around wily-nily, without thought, then it simply could not serve its purpose. Any time you dig into a smart pointer to get at the raw pointer inside you violate its ownership semantics and must then take very careful care not to go doing something stupid. Smart pointers make you think about this by forcing you to make a call to get at the pointer inside rather than just silently doing so whenever you accidentally pass it to the wrong kind of function. Think of it like the smart pointer saying, "Hey, you know what you're doing can be dangerous, right? OK then, here you go."

IMHO, the universe would be a better place if shared pointers didn't allow access to their pointers. Unfortunately, that's not this universe and can't be this universe because occasionally you still need to pass that thing into a function that doesn't use smart pointers. So, since we don't live in that better universe, OUR smart pointers do allow access, they're just not sluts about it.



回答2:

Perhaps there is a way, but it's kind a dirty one. You could try to put another class in your hierarchy: ABase. A could inherit from ABase, and ABase would contain default implementations of all the public methods. Something like this:

#include <iostream>
#include <boost/shared_ptr.hpp>

class ABase
{
    public:
        ABase() {}
        void init( ABase *a ) { mImpl.reset( a ); }

        virtual void setX( int x ) { mImpl->setX( x ); }
        virtual int getX() const { return mImpl->getX(); }

        boost::shared_ptr< ABase > mImpl;
};

class A : public ABase
{
public:
    typedef ABase BaseClass;

    A(int x) {mX = x;}
    virtual void setX(int x) {mX = x;}
    virtual int getX() const {return mX;}
private:
    int mX;
};


template <class T>
class Shared : public T::BaseClass
{
public:
    Shared(T* t) { init( t ); }
};

int main()
{
    Shared< A > sh( new A( 1 ) );

    std::cout << sh.getX() << std::endl;
    sh.setX( 5 );
    std::cout << sh.getX() << std::endl;
}

This is only a concept. I need to verify code. Key idea is to pass default implementation by inheriting shared not directly from type A, but from his base type, that has all implementations needed. Base class needs some more work, but I think the idea is pretty clear.

Its not exactly what you asked for (it's actually impossible), but it could be useful in some cases, and is probably closest you can get.



回答3:

Putting aside whether or not this is a good idea. If you want a shared-pointer type that can be implicitly converted to a raw pointer, you don't need to inherit from the pointee type or go through other such complications. Just make a shared-pointer type with the appropriate implicit conversion operators.

This could be done through inheritance or composition of an existing shared pointer type. (Or else by making your own shared pointer from scratch).

Using inheritance, for example:

template<typename T>
class convertible_shared_ptr : public boost::shared_ptr<T>
{
public:
    convertible_shared_ptr() {}
    convertible_shared_ptr(T* ptr) : boost::shared_ptr<T>(ptr) {}

    operator T* () const
    {
        return get();
    }

    T* operator-> () const
    {
        return get();
    }
};

You then use such a thing like so:

#include <iostream>
#include <boost/shared_ptr.hpp>
using namespace std;

class A    // simple class for example purposes
{
public:
    A() : member(42) {}

    int member;
};

void foo(A* a)
{
    cout << "a->member=" << a->member << endl;
}

int main()
{
    convertible_shared_ptr<A> ptr(new A);

    foo(ptr);

    cout << "ptr->member=" << ptr->member << endl;

    return 0;
}

Of course, I haven't actually tried such a thing in a real world scenario. There might be some complications when trying to interact with corresponding weak_ptr types. At the very least, there might be some more code necessary to cover all the possible usages.