1

I try to send to function a shared_ptr with polymorphic class. My objective is to find a best way to send my shared_ptr without increase ref_count.

EDIT: I don't search solution where my shared_ptr is replaced because I want to call shared_ptr.reset() for example.

Currently, void doGenericTemplate(std::shared_ptr<CLASS>& ptr) is what I want in result BUT I prefer a single function in program.

  1. Do you have another solution ?

Moreover, I don't understand why the function void doGeneric(std::shared_ptr<Base>& ptr) doesn't compile (equivalent without shared_ptr work fine: please check doClassic in complete code).

  1. Do you have an explain ?

Thanks you !

Partial code

#include <iostream>
#include <memory>

class Base
{
    public:
        Base() = default;
        virtual ~Base() = default;
        virtual void run() = 0;
};

class Derived1: public Base
{
    public:
        Derived1() = default;
        virtual ~Derived1() = default;
        void run()
        {
            std::cout << "  Derived1";
        }
};

class Derived2: public Base
{
    public:
        Derived2() = default;
        virtual ~Derived2() = default;
        void run()
        {
            std::cout << "  Derived2";
        }
};

// This function works but increase count
void doGenericCopy(std::shared_ptr<Base> ptr)
{
    ptr->run();
    std::cout << "  Ref count: " << ptr.use_count() << std::endl;
}

// This function works without increase count = OK !
void doSpecificD1(std::shared_ptr<Derived1>& ptr)
{
    ptr->run();
    std::cout << "  Ref count: " << ptr.use_count() << std::endl;
}

// Compilation error = FAILED !
void doGeneric(std::shared_ptr<Base>& ptr)
{
    ptr->run();
    std::cout << "  Ref count: " << ptr.use_count() << std::endl;
}

// Working fine for all Derivate = OK !
template<typename CLASS>
void doGenericTemplate(std::shared_ptr<CLASS>& ptr)
{
    ptr->run();
    std::cout << "  Ref count: " << ptr.use_count() << std::endl;
}

int main()
{
    auto d1 = std::make_shared<Derived1>();
    auto d2 = std::make_shared<Derived2>();

    std::cout << "With copy: " << std::endl;
    doGenericCopy(d1);
    doGenericCopy(d2);

    std::cout << "Specific: " << std::endl;
    doSpecificD1(d1);

    std::cout << "Template: " << std::endl;
    doGenericTemplate(d1);
    doGenericTemplate(d2);

    // Compilation issue
    //doGeneric(d1);
}

Complete code

https://ideone.com/ZL0v7z

Conclusion

Currently in c++, shared_ptr has not in language a specific tools to use polymorphism of class inside template.

The best way is to refactor my code and avoids to manage shared_ptr (ref_count, reset).

Thanks guys !

Rominitch
  • 174
  • 1
  • 1
  • 11
  • TL;DR. Didn't read the mass of the code, need MCVE to answer 'why function doesn't compile'. On the general question, passing `shared_ptr` by reference is fine, but often the better idea is to accept a direct reference to the parent class. – SergeyA Feb 06 '18 at 18:57
  • Duplicate https://stackoverflow.com/questions/13403490/passing-shared-ptrderived-as-shared-ptrbase – Peter Rindal Feb 06 '18 at 19:08
  • Possible duplicate of [Passing shared\_ptr as shared\_ptr](https://stackoverflow.com/questions/13403490/passing-shared-ptrderived-as-shared-ptrbase) – Peter Rindal Feb 06 '18 at 19:11

2 Answers2

2
  1. Do you have another solution ?

Pass object by reference or const reference instead of shared_ptr.

void doGeneric(Base& r)
{
    r.run();
}

Firstly - this shows explicitly that you do not store or cache pointer somwhere. Secondly - you avoid ambiguities like the one you presented here.

  1. Do you have an explain ?

Passing shared_ptr<Derived> to function causes implicit cast to shared_ptr<Base>. This new shared_ptr<Base> is temporary, so it can not be cast to shared_ptr<Base> &. This implicit cast would increase ref count even if you could pass it.

bartop
  • 9,971
  • 1
  • 23
  • 54
2

A shared_ptr<Base> and shared_ptr<Derived> are unrelated types, except you can implicitly create a shared_ptr<Base> from a shared_ptr<Derived>.

This creation adds a reference count.

If you really, really want to avoid that reference count...

template<class T>
struct shared_ptr_view {
  template<class D>
  shared_ptr_view( std::shared_ptr<D>& sptr ):
    vtable( get_vtable<D>() ),
    ptr( std::addressof(sptr) )
  {}
  shared_ptr_view( shared_ptr_view const& ) = default;
  shared_ptr_view() = default;
  shared_ptr_view& operator=( shared_ptr_view const& ) = delete;
  T* get() const { if(vtable) return vtable->get(ptr); return nullptr; }
  void clear() const { if(vtable) vtable->clear(ptr); }
  std::shared_ptr<T> copy() const { if(vtable) return vtable->copy(ptr); return {} }
  operator std::shared_ptr<T>() const { return copy(); }
  T* operator->() const { return get(); }
  T& operator*() const { return *get(); }
  explicit operator bool() const { return get(); }
  std::size_t use_count() const { if (vtable) return vtable->use_count(ptr); return 0; }
private:
  struct vtable_t {
    T*(*get)(void*) = 0;
    std::shared_ptr<T>(*copy)(void*) = 0;
    void(*clear)(void*) = 0;
    std::size_t(*use_count)(void*) = 0;
  };
  vtable_t const* vtable = 0;
  void* ptr = 0;
  template<class D>
  static vtable_t create_vtable() {
    return {
      [](void* ptr)->T*{ return static_cast<std::shared_ptr<D>*>(ptr)->get(); },
      [](void* ptr)->std::shared_ptr<T>{ return *static_cast<std::shared_ptr<D>*>(ptr); },
      [](void* ptr){ static_cast<std::shared_ptr<D>*>(ptr)->reset(); },
      [](void* ptr){ return static_cast<std::shared_ptr<D>*>(ptr)->use_count(); }
    };
  }
  template<class D>
  static vtable_t const* get_vtable() {
    static const auto vtable = create_vtable<D>();
    return &vtable;
  }
};

then

void doGeneric( shared_ptr_view<Base> ptr ) {
  ptr->run();
  std::cout << "  Ref count: " << ptr.use_count() << std::endl;
}

does not increase the reference count. I think it is raw insanity.

shared_ptr_view.clear() works, but shared_ptr_view.reset(T*) cannot: a shared_ptr_view<Derived> cannot be reset to point to a Base*.

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