HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppModerate

'any' class implementation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
implementationanyclass

Problem

I have made an any class in C++, base loosely on boost::any, but written differently. I am checking to see if I have done it correctly and that there are no mistakes in it:

```
class any
{
public:
any()
: dt(new data(0))
{
}

template
any(const T &value)
: dt(new data(value))
{
}

any(any &rhs)
: dt(rhs.dt->duplicate())
{
}

~any()
{
delete dt;
}

template
T cast() const
{
if (type() == typeid(T))
{
return (reinterpret_cast *>(dt)->val);
}
throw std::exception("invalid cast type");
}

template
operator T() const
{
return (cast());
}

template
bool is() const
{
return (type() == typeid(T));
}

any &operator=(any &rhs)
{
if (this != &rhs)
{
delete dt;
dt = rhs.dt->duplicate();
}
return (*this);
}

template
any &operator=(const T &value)
{
delete dt;
dt = new data(value);
return (*this);
}

any &swap(any &rhs)
{
std::swap(dt, rhs.dt);
return (*this);
}

template
bool operator==(const T &value) const
{
return (type() == typeid(T) &&
cast() == value);
}

bool operator==(any &rhs) const
{
return (type() == rhs.type() &&
dt->cmp(rhs.dt));
}

template
bool operator!=(const T &value) const
{
return (!((*this) == value));
}

bool operator!=(any &rhs) const
{
return (!((*this) == rhs));
}

const std::type_info &type() const
{
return (dt->type());
}

protected:
struct dummy
{
public:
virtual const std::type_info &type() const = 0;

Solution

Your code is in pretty much good shape but there are still several issues apart from what mentioned by ChrisW:

  • There are many cases in input arguments and return types of functions where you are not particularly careful about const/non-const and value vs. reference.



  • This code won't work for built-in arrays, hence neither for C-style strings. One way is to decay the type of the input argument before storage; built-in arrays are decayed to pointers in this case (which means array elements are not really copied).



  • The default constructor shouldn't allocate anything; initialize the data pointer to nullptr and provide member functions empty() and clear() to control the state of having/not having data.



  • Your assignment operators are unnecessarily complex, inefficient (by self-tests) and not exception-safe (if new throws, the current object is already destroyed). The most elegant solution to all these issues is the copy-swap idiom, where all actual work is done by constructors alone.



  • You don't need typeid to test for type equality; a lightweight (but low-level) solution without RTTI is here.



  • Type identification should be kept as an internal detail; the minimal required functionality is type equality by is(); don't expose type(), rather keep it as private as possible.



  • Type checking is a good thing, but for performance you should also provide unchecked access.



  • Casting is from a base to a derived class, so need not (and should not) be done with reinterpret_cast; rather, dynamic_cast / static_cast for checked / unchecked access. dynamic_cast to a reference type will automatically throw an std::bad_cast if the object is not of the right type, so there is not need to manually check with is(). This does need RTTI but is more elegant.



  • Storing empty objects (like function objects) is currently inefficient, as it does need extra space on top of the virtual function table. This can be solved by the empty base optimization, which is done automatically by using std::tuple.



  • Implicit conversion operators are a possible source for ambiguities and confusion; you may keep them if you need their convenience, but use carefully (e.g. try to explicitly initialize an object of the right type).



  • Comparison operators are a clear overkill (if you have them, why not also have arithmetic operators, and so on?). If you still want them, define them as non-member functions, using public members is and cast to implement them.



  • There are no move semantics.



  • There is no specialized binary (non-member) function swap. Defaulting to std::swap is not as efficient as it involves three move operations; without move semantics, things are even worse as it involves three copy operations.



I took the liberty to re-factor your code to a great extent, and here is the result, resolving all issues above:

```
class some
{
using id = size_t;

template
struct type { static void id() { } };

template
static id type_id() { return reinterpret_cast(&type::id); }

template
using decay = typename std::decay::type;

template
using none = typename std::enable_if::value>::type;

struct base
{
virtual ~base() { }
virtual bool is(id) const = 0;
virtual base *copy() const = 0;
} *p = nullptr;

template
struct data : base, std::tuple
{
using std::tuple::tuple;

T &get() & { return std::get(*this); }
T const &get() const& { return std::get(*this); }

bool is(id i) const override { return i == type_id(); }
base *copy() const override { return new data{get()}; }
};

template
T &stat() { return static_cast&>(*p).get(); }

template
T const &stat() const { return static_cast const&>(*p).get(); }

template
T &dyn() { return dynamic_cast&>(*p).get(); }

template
T const &dyn() const { return dynamic_cast const&>(*p).get(); }

public:
some() { }
~some() { delete p; }

some(some &&s) : p{s.p} { s.p = nullptr; }
some(some const &s) : p{s.p->copy()} { }

template, typename = none>
some(T &&x) : p{new data{std::forward(x)}} { }

some &operator=(some s) { swap(this, s); return this; }

friend void swap(some &s, some &r) { std::swap(s.p, r.p); }

void clear() { delete p; p = nullptr; }

bool empty() const { return p; }

template
bool is() const { return p ? p->is(type_id()) : false; }

template T &&_() && { return std::move(stat()); }
template T &_() & { return stat(); }
template T const &_() const& { return stat(); }

template T &&cast() && { return std::move(dyn()); }
template T &cast() & { return dyn(); }
template T const &cast() const& { return dyn(); }

template operator T &&() && { return std::move(_()); }
template operator T &() & { return _(); }
template operator T const&() const& { return _();

Code Snippets

class some
{
    using id = size_t;

    template<typename T>
    struct type { static void id() { } };

    template<typename T>
    static id type_id() { return reinterpret_cast<id>(&type<T>::id); }

    template<typename T>
    using decay = typename std::decay<T>::type;

    template<typename T>
    using none = typename std::enable_if<!std::is_same<some, T>::value>::type;

    struct base
    {
        virtual ~base() { }
        virtual bool is(id) const = 0;
        virtual base *copy() const = 0;
    } *p = nullptr;

    template<typename T>
    struct data : base, std::tuple<T>
    {
        using std::tuple<T>::tuple;

        T       &get()      & { return std::get<0>(*this); }
        T const &get() const& { return std::get<0>(*this); }

        bool is(id i) const override { return i == type_id<T>(); }
        base *copy()  const override { return new data{get()}; }
    };

    template<typename T>
    T &stat() { return static_cast<data<T>&>(*p).get(); }

    template<typename T>
    T const &stat() const { return static_cast<data<T> const&>(*p).get(); }

    template<typename T>
    T &dyn() { return dynamic_cast<data<T>&>(*p).get(); }

    template<typename T>
    T const &dyn() const { return dynamic_cast<data<T> const&>(*p).get(); }

public:
     some() { }
    ~some() { delete p; }

    some(some &&s)      : p{s.p} { s.p = nullptr; }
    some(some const &s) : p{s.p->copy()} { }

    template<typename T, typename U = decay<T>, typename = none<U>>
    some(T &&x) : p{new data<U>{std::forward<T>(x)}} { }

    some &operator=(some s) { swap(*this, s); return *this; }

    friend void swap(some &s, some &r) { std::swap(s.p, r.p); }

    void clear() { delete p; p = nullptr; }

    bool empty() const { return p; }

    template<typename T>
    bool is() const { return p ? p->is(type_id<T>()) : false; }

    template<typename T> T      &&_()     && { return std::move(stat<T>()); }
    template<typename T> T       &_()      & { return stat<T>(); }
    template<typename T> T const &_() const& { return stat<T>(); }

    template<typename T> T      &&cast()     && { return std::move(dyn<T>()); }
    template<typename T> T       &cast()      & { return dyn<T>(); }
    template<typename T> T const &cast() const& { return dyn<T>(); }

    template<typename T> operator T     &&()     && { return std::move(_<T>()); }
    template<typename T> operator T      &()      & { return _<T>(); }
    template<typename T> operator T const&() const& { return _<T>(); }
};

Context

StackExchange Code Review Q#47975, answer score: 12

Revisions (0)

No revisions yet.