1

When creating template classes in C++, if the type upon which the template will be specialized is intended to be a shared_ptr type, is it better to make T a shared_ptr type or make T a non-pointer type?

For example, which of the below classes is better, FirstExample or SecondExample? Or alternatively, is this entirely context-dependent? If so, can you an example of a context where FirstExample is better and a context where SecondExample is better?

In my case, clarity of usage is a higher priority than performance.

#include <iostream>
#include <memory>

using IntPtr = std::shared_ptr<int>;

template <class T>
class FirstExample
{

public:
    FirstExample( std::shared_ptr<T> value )
    :myData( value ) {}
    std::shared_ptr<T> getData() const { return myData; }

private:
    std::shared_ptr<T> myData;
};

template <class T>
class SecondExample
{

public:
    SecondExample( const T& value )
    :myData( value ) {}

    T getData() const { return myData; }

private:
    T myData;
};



int main(int argc, const char * argv[])
{
    FirstExample<int> firstInstance( std::make_shared<int>( 10 ) );
    IntPtr copyOfFirstInstanceData = firstInstance.getData();

    SecondExample<IntPtr> secondInstance( std::make_shared<int>(10) );
    IntPtr copyOfSecondInstanceData = secondInstance.getData();

    return 0;
}
3

Let's first adjust the user-defined ctors for equivalence:

  1. Use pass-by-value and move-construction:

    FirstExample( std::shared_ptr<T> value ) : myData(std::move(value)) {}
    SecondExample( T value ) : myData(std::move(value)) {}
    
  2. Use pass-by-reference and copy-construction:

    FirstExample( const std::shared_ptr<T>& value ) : myData(value) {}
    SecondExample( const T& value ) : myData(value) {}
    
  3. Use pass-by-reference and move-/copy-construction:

    FirstExample( const std::shared_ptr<T>& value ) : myData(value) {}
    SecondExample( const T& value ) : myData(value) {}
    FirstExample( std::shared_ptr<T>&& value ) : myData(std::move(value)) {}
    SecondExample( T&& value ) : myData(std::move(value)) {}
    

As an aside, I would change getData too, in order to avoid copyng where not needed:

const T& getData() const { return myData; }
const std::shared_ptr<T>& getData() const { return myData; }

Now, ThirdExample behaves the same as FirstExample:

template<class T> using ThirdExample = SecondExample<std::shared_ptr<T>>;

The advantage to using the first one is less typing and possibly better error-messages.
The advantage to using the second is more flexibility, as you can use another smart-pointer, as long as it offers the proper interface.

But why should you have to decide between either?
As ThirdExample demonstrates, you can implement the second example, and provide the first as a simple alias-template for convenience, just like the standard-library does for std::integer_sequence and std::index_sequence.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.