I have implemented a basic object
class in C++, and I want to make sure that I have implemented everything efficiently and safely, as I made a few mistakes while making it, which I corrected, but I could have left a few more mistakes behind:
class object
{
private:
class dummy
{
public:
dummy()
{
}
virtual ~dummy()
{
}
virtual const std::type_info &type() = 0;
virtual dummy *duplicate() const = 0;
virtual bool eq(object &) = 0;
};
template < typename _Ty > class data : public dummy
{
public:
data()
{
}
data(const _Ty &_Value)
: __data(_Value)
{
}
~data()
{
}
const std::type_info &type()
{
return typeid(_Ty);
}
data *duplicate() const
{
return new data<_Ty>(__data);
}
bool eq(object &_Obj)
{
return _Obj.cast<_Ty>() == __data;
}
_Ty __data;
};
dummy *d;
public:
object()
{
}
template < typename _Ty > object(const _Ty &_Value)
: d(new data<_Ty>(_Value))
{
}
object(const object &_Obj)
: d(_Obj.d->duplicate())
{
}
~object()
{
if (!empty())
{
delete d;
}
}
const std::type_info &type()
{
return (empty() ? typeid(void) : d->type());
}
object &operator=(object &_Rhs)
{
d = _Rhs.d->duplicate();
return *this;
}
object &swap(object &_Rhs)
{
std::swap(*this, _Rhs);
return *this;
}
template < typename _Ty > object &operator=(const _Ty &_Value)
{
d = new data<_Ty>(_Value);
return *this;
}
template < typename _Ty > _Ty cast()
{
if (type() == typeid(_Ty))
{
return static_cast<data<_Ty> *>(d)->__data;
}
throw std::exception("Invalid cast type");
}
bool operator==(object &_Rhs)
{
return (type() == _Rhs.d->type() ? d->eq(_Rhs) : false);
}
template < typename _Ty > bool operator==(_Ty _Value)
{
return (type() == typeid(_Ty) ? cast<_Ty>() == _Value : false);
}
bool operator!=(object &_Rhs)
{
return !(*this == _Rhs);
}
template < typename _Ty > bool operator!=(const _Ty &_Value)
{
return !(*this == _Value);
}
bool empty()
{
return !d;
}
};
2 Answers 2
object &operator=(object &_Rhs)
{
d = _Rhs.d->duplicate();
return *this;
}
I think you're leaking your previous d
object.
It's also conventional to verify whether _Rhs
and this
are the same (and to do nothing if they are), otherwise it will misbehave if you assign an object to itself (like foo = foo;
).
-
2\$\begingroup\$ Or to use copy-and-swap instead. \$\endgroup\$Yuushi– Yuushi2014年02月25日 01:26:24 +00:00Commented Feb 25, 2014 at 1:26
As per common naming convention, the class names should start with a capital letter.
It may be more readable to put the
template
line above theclass
line:template < typename _Ty > class data : public dummy { }
Your
bool
functions should beconst
. Any member function that doesn't modify data members should beconst
. Such functions also include accessors ("getters") and thebool
operators (operator==
,operator!=
,operator<
, andoperator>
).For instance, with
empty()
:bool empty() const { // ... }
and
operator==
:bool operator==(object &_Rhs) const { // ... }
Also, the above parameter should be
const&
since_Rhs
is not being modified.If you're not defining your own constructor and/or destructor, you don't need to make them yourself. The compiler will make them for you.
However, if you're using C++11, you can have an explicitly defaulted constructor and destructor:
data() = default; ~data() = default;
-
2\$\begingroup\$ A default constructor will be created for you if no other constructor exists. I think it's always clearer to use
default
for this reason - it shows intent to have a default constructor. \$\endgroup\$Yuushi– Yuushi2014年02月25日 01:28:09 +00:00Commented Feb 25, 2014 at 1:28 -
\$\begingroup\$ @Yuushi: Oh, you mean like
data() = default;
? If I'm correct, it's for C++11. \$\endgroup\$Jamal– Jamal2014年02月25日 01:43:21 +00:00Commented Feb 25, 2014 at 1:43 -
\$\begingroup\$ "As per common naming convention, the class names should start with a capital letter." Like the standard library? \$\endgroup\$Sebastian Redl– Sebastian Redl2014年02月25日 13:02:57 +00:00Commented Feb 25, 2014 at 13:02
-
\$\begingroup\$ @SebastianRedl Stroustrup suggests that you capitalize user-defined names in TC++PLv4p156. He then continues, "Note that the language and the standard library use lowercase for types; this can be seen as a hint that they are part of the standard." \$\endgroup\$dyp– dyp2014年02月25日 21:19:03 +00:00Commented Feb 25, 2014 at 21:19
-
\$\begingroup\$ @Jamal - the only point I disagree with in your answer is this: "As per common naming convention, the class names should start with a capital letter". In all C++ projects I've worked in, the naming convention was never based on Stroustrup's suggestion; instead, it was either MFC/hungarian convention (yuk!), or
lower_case_with_underscores
because of uniformity of client code and readability (or similar arguments), or a custom convention (e.g.int mylib_ApiName(const int in_value, int &out_value);
or similar). \$\endgroup\$utnapistim– utnapistim2014年03月03日 10:50:07 +00:00Commented Mar 3, 2014 at 10:50
{ }
with= default;
2) Get rid of names starting with underscore(s) \$\endgroup\$object(){}
will default-initialize the data memberdummy *d;
. Default-initialization for fundamental types means no initialization is performed. Subsequent calls ofempty()
might returnfalse
. Use value-initializationobject() : d() {}
or explicit initializationobject() : d(nullptr) {}
. Also, you don't need to checkif(!empty())
beforedelete d;
; deleting a nullptr value is safe. \$\endgroup\$std::swap(*this, _Rhs);
won't do any magic. It will make a three copies (one copy-construction of a "temporary", and two copy-assignments). That's exactly what you want to avoid when supplying a customswap
function. Also, you should consider making it a non-member friend function, see stackoverflow.com/a/5695855/420683 \$\endgroup\$