views:

353

answers:

5

I need to create a generic object carrier class. I came up with something simple like

template<typename T>
class ObjectCarrier
{

public:
    const T& item() const
    {
     return item_;
    }

    void setItem(T& item)
    {
     item_ = item;
    }

private:
    T item_;
};

This works well when T has got a default constructor (parameterless). Things gets complicated when T has parameterized constructors. So I rewrote the class like

template<typename T>
class ObjectCarrier
{

public:
    const T& item() const
    {
     return *item_;
    }

    void setItem(T& item)
    {
     item_ = new T ( item );
    }

private:
    T* item_;
};

Changed the item_ variable to T* and created a new instance using the copy constructor of T. Again this worked well until T is a pointer type. I mean ObjectCarrier<Foo*> won't work.

I am wondering how can I design this class so that it works for almost all kind of types. I think I may need to create a traits type specialized for pointers. But unfortunately, I am not able to make that work.

Any help would be great.

A: 

You can use template specialization for the T* type and rewrite the methods to suite pointers. You can do something like:

template<typename T>
class ObjectCarrier<T*>
{
    public:
    const T* item() const
    {
        return item_;
    }

    void setItem(T* item)
    {
        item_ = item;
    }

private:
    T* item_;

};
Naveen
You mean specialize `ObjectCarrier` itself for pointers?
Appu
This is OK for small classes like the `ObjectCarrier`. But is this the only option? I think this will be tough when creating custom collections that will have several methods. How STL implements this? Do they have specializations of `vector` for all types? Thanks for replying.
Appu
vector requires objects to be default-constructible so this problem doesn't arise. There is a specialization of vector for vector<bool> to implement a sort of bitfields, although that implementation is dangerous to use.
Naveen
@Naveen, Maybe someone with a standard can comment but as far as I know std::vector doesn't require a default constructor. You can try it yourself easily, I just did and no compile time error even with no default constructor.
DeusAduro
@DeusAduro: You are right, only when I am using the vector constructor with initial size, we require a default constructor. Otherwise it is not required.
Naveen
A: 

There is a design patern that is possibly relevant to this - Memento.

A bit off topic, but bear in mind that as soon as you start newing objects up inside your class, you'll need a way to manage the memory. I'd suggest using an std::auto_ptr at the least. You'll also need to provide a copy constructor and an assignment operator, when using std::auto_ptr.

Chris Bednarski
A: 

It might be possible to hold the object by value and still defer its construction with the use of placement new and something like the following:

#include <iostream>
#include <cassert>

template <class T>
class ObjectCarrier
{
public:
    ObjectCarrier(): ref(0) {}
    ObjectCarrier(const ObjectCarrier& other): ref(0)
    {
        set_data(other.ref);
    }
    ~ObjectCarrier()
    {
        clear();
    }
    const ObjectCarrier& operator = (const ObjectCarrier& other)
    {
        if (other.empty())
            clear();
        else
            set_data(other.ref);
        return *this;
    }
    void set(const T& value)
    {
        set_value(value);
    }
    const T& get() const
    {
        assert(!empty() && "No object being carried");
        return *ref;
    }
    bool empty() const
    {
        return ref == 0;
    }
    void clear()
    {
        if (!empty()) {
            ref->~T();
            ref = 0;
        }
    }
private:
    char data[sizeof(T)];
    T* ref;
    void set_value(const T& value)
    {
        if (!empty()) {
            *ref = value;
        }
        else {
            ref = new (data) T(value);
        }
    }
    void set_data(const T* value)
    {
        if (value) {
            set_value(*value);
        }
    }
};

int main()
{
    ObjectCarrier<int> i;
    ObjectCarrier<int> j(i);
    i = j;
    i.set(10);
    std::cout << i.get() << '\n';
    j = i;
    i.set(20);
    std::cout << i.get() << ' ' << j.get() << ' ' << ObjectCarrier<int>(i).get() << '\n';
}

However, I would somewhat question the usefulness of this class. Perhaps the only purpose it could have, would be to act as Boost.Optional.

But if you don't want the class to be able to not hold a value, just give it a parametrized constructor:

template<typename T>
class ObjectCarrier
{

public:
    ObjectCarrier(const T& value = T()):
        item_(value)
    {
    }
    const T& item() const
    {
        return item_;
    }

    void setItem(T& item)
    {
        item_ = item;
    }

private:
    T item_;
};

(It's just that this class seems rather useless, unless perhaps as a facade for code that expects variables to have item and setItem methods, rather than, say, an assignment operator.)

UncleBens
Your first idea might need a hint at alignment problems.
sbi
Sorry, what alignment problems?
UncleBens
Maybe I'm missing something, but when you write `char data[sizeof(T)]`, how do you know the array will be aligned for an object of type `T`?
sbi
I don't. Alignment issues is something I haven't run into (but then I don't do weird things). Wouldn't `ObjectCarrier<T>` itself be aligned and data as the first member (not that I can entirely see why that should matter) with it?
UncleBens
@UncleBens: The problem is that `char data[sizeof(T)];`. That's going to be aligned for `char`, but not for `T`. On popular platforms this isn't a problem (although misaligned data on x86 will cause a performance loss, IIRC), but when you're going to port thsi to platforms where there are relevant alignment differences between `char` and `T`, you will have a problem. Currently, C++ offers nothing to solve this std-conforming. People play all kind of tricks using `union` which are reasonably portable. ISTR that C++1x will solve this portably, but I'm not sure.
sbi
A: 

boost::optional does something very similar to this (also boost::any, but nevermind). You can check out how its implemented at: http://cplusplus.co.il/2009/12/04/boost-optional-and-its-internals/ and don't worry - it's pretty straightforward.

rmn
+1  A: 

The above approaches are way way too complicated. Keep it simple, and just solve the constructor arg problem by using template constructors. Don't use pointers, they will create object lifetime and copying headaches.

Here's an implementation I use a lot. The template constructors will forward arguments for things directly on to the nested object which is convenient. The operator T& values let you pass carrier<T> to functions that take a type T, without expensive copying. You can wrap objects that take up to two arguments with this code.

/* A wrapper of type T */
template <typename T>
struct carrier {

   carrier() {}
   template <typename A1> carrier(const A1& a1) : value(a1) {}
   template <typename A1, typename A2> carrier(const A1& a1, const A2& a2) : value(a1, a2) {}

   operator T&() { return value; }
   operator const T&() const { return value; }

   T value;
};

You can use it like this:

const carrier<point> p1(10,10);   // make p1 const to stop people changing it
showPoint(p1);                    // calls a function that expects a point,
showPoint(p1.value);              // access the point directly
Matthew Herrmann