简体   繁体   中英

Difference between two ways of defining class template constructors

I'm trying to implement my custom version of shared_ptr and weak_ptr. While implementing them, I've got into some problems with it.

In order to accept subclass types from constructor, I had to use another template type U as parameter other than template type of the class 'T'.

Let's say constructor with parameter type U as version 1 and constructor with parameter type T as version 2

Here's my question

  • Why does version 2 gets called when I initialize class template with same type?
WeakPtr<T> weakPtr_a;
WeakPtr<T> weakPtr_b = weakPtr_a; //Shouldn't this be still enough with version 1?
  • In which cases does version 2 gets called? Is this implementation feasible?

Here are my code snippets

WeakPtr-Decl.hpp

template <typename T>
class WeakPtr
{
    T* m_objectPtr = nullptr;

    SharedObjectInfo* m_sharedObjectInfoPtr = nullptr;

    template <typename U>
    friend class WeakPtr;

 public:
    constexpr WeakPtr();

    ~WeakPtr() = default;

    //! Copy constructor
    //! \tparam U : template type of weakPtr to copy from
    //! U must be same type as T or subclass of T or assertion will fail
    //! \param weakPtr : weakPtr to copy from
    template <typename U>
    WeakPtr(const WeakPtr<U>& weakPtr); // Version 1

    //! Copy constructor
    //! \param weakPtr : weakPtr to copy from
    WeakPtr(const WeakPtr<T>& weakPtr); // Version 2
};

WeakPtr-Impl.hpp

template <typename T>
template <typename U>
WeakPtr<T>::WeakPtr(const WeakPtr<U>& weakPtr)
    : m_objectPtr(weakPtr.m_objectPtr),
      m_sharedObjectInfoPtr(weakPtr.m_sharedObjectInfoPtr)
{
    static_assert(std::is_same<std::decay_t<T>, std::decay_t<U>>::value ||
                  std::is_base_of<std::decay_t<T>, std::decay_t<U>>::value);
}

template <typename T>
WeakPtr<T>::WeakPtr(const WeakPtr<T>& weakPtr)
    : m_objectPtr(weakPtr.m_objectPtr),
      m_sharedObjectInfoPtr(weakPtr.m_sharedObjectInfoPtr)
{
}

As explained in comments, when available an exact non-template constructor (version 2, in your case), it's preferred over a template one (version 1).

I suggest to use delegating constructor, so let the compiler choose the version 2 (when the type is the same) and, from version 2, call version 1.

I propose to add an unused defaulted second parameter in version 1

  template <typename U> // ............VVVVVVV
  WeakPtr (WeakPtr<U> const & weakPtr, int = 0)
     : m_objectPtr{weakPtr.m_objectPtr},
       m_sharedObjectInfoPtr{weakPtr.m_sharedObjectInfoPtr}
   {  }

and transform version 2 as follows

  WeakPtr (WeakPtr const & weakPtr) : WeakPtr{weakPtr, 0}
   { }

The second unused parameter in version 1 is added to permit to select version 1 calling it from version 2.

Observe the zero in WeakPtr{weakPtr, 0} in delegating call in version 2: the added zero force the call of version 1 because version 2 has only one argument.

The default value is important to permit the direct use of version 1 when the types are different.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM