views:

463

answers:

4

I have a few containers in a class, for example, vector or map which contain shared_ptr's to objects living on the heap.

For example

template <typename T>
class MyExample
{
public:

private:
 vector<tr1::shared_ptr<T> > vec;
 map<tr1::shared_ptr<T> , int> h;
};

I want to have a public interface of this class that sometimes returns shared_ptrs to const objects (via shared_ptr) and sometimes shared_ptr where I allow the caller to mutate the objects. I want logical const correctness, so if I mark a method as const, it cannot change the objects on the heap.

Questions:

1) I am confused by the interchangeability of tr1::shared_ptr<const T> and tr1::shared_ptr<T>. When someone passes a shared_ptr<const T> shared_ptr into the class, do I store it as a shared_ptr<T> or shared_ptr<const T> inside the vector and map or do I change the map, vector types (e.g. insert_elemeent(shared_ptr<const T> obj) ?

2) Is it better to instantiate classes as follows: MyExample<const int> ? That seems unduly restrictive, because I can never return a shared_ptr<int> ?

+1  A: 

one thing to realize is that:

tr1::shared_ptr<const T> is mimicking the functionality of T const * namely what it points to is const, but the pointer itself isn't.

So you can assign a new value to your shared pointer, but I would expect that you wouldn't be able to use the dereferenced shared_ptr as an l-value.

Evan Teran
A: 

If someone passes you a shared_ptr<const T> you should never be able to modify T. It is, of course, technically possible to cast the const T to just a T, but that breaks the intent of making the T const. So if you want people to be able to add objects to your class, they should be giving you shared_ptr<T> and no shared_ptr<const T>. When you return things from your class you do not want modified, that is when you use shared_ptr<const T>.

shared_ptr<T> can be automatically converted (without an explicit cast) to a shared_ptr<const T> but not the other way around. It may help you (and you should do it anyway) to make liberal use of const methods. When you define a class method const, the compiler will not let you modify any of your data members or return anything except a const T. So using these methods will help you make sure you didn't forget something, and will help users of your class understand what the intent of the method is. (Example: virtual shared_ptr<const T> myGetSharedPtr(int index) const;)

You are correct on your second statement, you probably do not want to instantiate your class as <const T>, since you will never be able to modify any of your Ts.

SoapBox
+6  A: 

shared_ptr<T> and shared_ptr<const T> are not interchangable. It goes one way - shared_ptr<T> is convertable to shared_ptr<const T> but not the reverse.

Observe:

// f.cpp

#include <memory>

int main()
{
    using namespace std;

    shared_ptr<int> pint(new int(4)); // normal shared_ptr
    shared_ptr<const int> pcint = pint; // shared_ptr<const T> from shared_ptr<T>
    shared_ptr<int> pint2 = pcint; // error! comment out to compile
}

compile via

cl /EHsc f.cpp

You can also overload a function based on a constness. You can combine to do these two facts to do what you want.

As for your second question, MyExample<int> probably makes more sense than MyExample<const int>.

Terry Mahaffey
A: 

I would suggest the following methotology:

template <typename T>
class MyExample
{
  private:
    vector<shared_ptr<T> > data;

  public:
    shared_ptr<const T> get(int idx) const
    {
      return data[idx];
    }
    shared_ptr<T> get(int idx)
    {
      return data[idx];
    }
    void add(shared_ptr<T> value)
    {
      data.push_back(value);
    }
};

This ensures const-correctness. Like you see the add() method does not use <const T> but <T> because you intend the class to store Ts not const Ts. But when accessing it const, you return <const T> which is no problem since shared_ptr<T> can easily be converted to shared_ptr<const T>. And sice both get() methods return copies of the shared_ptr's in your internal storage the caller can not accidentally change the object your internal pointers point to. This is all comparable to the non-smart pointer variant:

template <typename T>
class MyExamplePtr
{
  private:
    vector<T *> data;

  public:
    const T *get(int idx) const
    {
      return data[idx];
    }
    T *get(int idx)
    {
      return data[idx];
    }
    void add(T *value)
    {
      data.push_back(value);
    }
};
rstevens
Should it not be that `shared_ptr<T>` can easily be convertedto shared_ptr<const T>` and not the other way around?
You're right! Just a typo!
rstevens