I am writing a template of a working class, and this might just be a dumb question, but if I have a template structure (linked list) to hold possibly pointers to objects then how do I know that they are being deleted, or that they where pointers in the first place?
for example: the linkedList will be used in 2 ways in this program
a pointer to an object of class Thing is placed inside a node inside a linkedList
an enum is placed inside a node inside a linkedList
I know that the nodes are being deleted, but how do I know that the thing in the node is a pointer so that it can be deleted as well, and not just be a Null referenced object?
You can specialize the node based on the type of the object, and for the pointer specialization, create a destructor for the node-type that properly allocates and deletes the pointer managed by the node.
For instance:
//general node type for non-pointer types
template<typename T>
struct linked_list_node
{
T data;
linked_list_node<T>* next;
linked_list_node(const T& d): data(d), next(NULL) {}
~linked_list_node() {}
};
//specialized version for pointer types
template<typename T>
struct linked_list_node<T*>
{
typedef void (*deleter)(T*);
T* data;
linked_list_node<T>* next;
deleter d_func; //custom function for reclaiming pointer-type
linked_list_node(const T& d): data(new T(d)), next(NULL), d_func(NULL) {}
linked_list_node(const T& d, deleter func): data(new T(d)),
next(NULL), d_func(func) {}
~linked_list_node()
{
if(d_func)
d_func(data); //execute custom function for reclaiming pointer-type
else
delete data;
}
};
You can then instantiate the different versions by passing the correct template argument when creating an instance of the linked_list_node type. For instance,
linked_list_node<MyPtr*> node(FooPtr); //creates the specialized ptr version
linked_list_node<MyEnum> node(FooEnum); //creates a non-ptr version of the node
Template specialization is the best answer, and will work well as long as you don't mix types of nodes. However if you want to mix types of your linked nodes, let me show you how to do it. First, there is no straightforward template solution. You would have to type cast your linked nodes together due to strict type constrains.
A quite common solution is to construct a variant class (which can hold one value with variant types, and is always aware which one). Qt has a QVariant class, for instance. Boost has boost::any.
Here is a complete example implementation using a custom variant class that could hold any of your types. I can handle your suggested object pointer and enum, but could be extended to hold more.
An example which will print "delete obj" once:
#include <iostream>
int
main( int argc, char **argv )
{
LinkedList<VariantExample> elementObj( new ExampleObj );
LinkedList<VariantExample> elementEnum( enumOne );
elementEnum.setNext( elementObj );
}
// VariantExample class. Have a look at [QVariant][4] to see how a fairly
// complete interface could look like.
struct ExampleObj
{
};
enum ExampleEnum
{
enumOne,
enumTwo
};
struct VariantExample
{
ExampleObj* obj; // or better boost::shared_ptr<ExampleObj> obj
ExampleEnum en;
bool is_obj;
bool is_enum;
VariantExample() : obj(0), is_obj(false), is_enum(false) {}
// implicit conversion constructors
VariantExample( ExampleObj* obj_ ) : is_obj(true), is_enum(false)
{ obj = obj_;
}
VariantExample( ExampleEnum en_ ) : obj(0), is_obj(false), is_enum(true)
{ en = en_;
}
// Not needed when using boost::shared_ptr above
void
destroy()
{
if( is_obj && obj )
{
std::cout << "delete obj" << std::endl;
delete obj;
}
}
};
// The linked list template class which handles variant classes with a destroy()
// method (see VariantExample).
template
<
typename _type_ = VariantExample
>
struct LinkedList
{
LinkedList* m_next;
_type_ m_variant;
explicit
LinkedList( _type_ variant_ ) : m_next(0), m_variant( variant_ ){ }
void
setNext( LinkedList& next_ ){ m_next = &next_; }
// Not needed when using boost::shared_ptr above
~LinkedList()
{
m_variant.destroy();
}
};
Because elementObj's destroy method called once when the LinkedList's destructor is called, the output "delete obj" is appearing just once. Again, as you were quite specific about the delete/ownership, this example has a destroy method/interface. It will be explicitly called in the destructor of the LinkedList class. A better ownership model could be implemented with ie. boost::shared_ptr. Then you dont need to destroy it manually. It helps to read about conversion constructors, by the way.
// the first parameter becomes boost::shared_ptr<ExampleObj>( new ExampleObj ) )
// and is deleted when LinkedList is destroyed. See code comments above.
LinkedList<> elementObj( new ExampleObj );
Finally note that you have to have a single variant class to hold all your types which could appear in your LinkedList chain. Two different LinkedList Variant types would not work, again, finally because of the "next" pointer type; which would be not compatible.
Footnote:
How type constrains prevent an easy solution ? Imagine your linked node "next" pointer type is not just the the bare template name, its a shortcut, but is actually qualified including the template arguments - what end up as the type symbol the compiler uses to judge type compabilities.
Related
I am porting to C++11 a C code base that makes use of a number of custom intrusive data structures.
In C, the usage patterns will typically look like this:
struct foo {
// some members
struct data_structure_node node;
};
// user code
struct *foo = NULL;
struct data_structure_node *result = find_in_data_structure(data_structure, some_key);
if (node) {
foo = container_of(result, struct data_structure_node, node);
// use foo
}
Here, container_of is implemented much like in the Linux kernel:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
As the code moves to more idiomatic C++, structures like foo typically end up becoming classes that use different access controls, virtual functions, etc. This, in turn, makes them adopt a non standard layout and causes GCC and clang to emit the following warning when container_of is used:
error: 'offsetof' within non-standard-layout type 'foo' is conditionally-supported [-Werror=invalid-offsetof]
I have been pondering how to implement a safe alternative to the container_of macro. Using a pointers to data member is the first idea that came to my mind and I'm considering replacing uses of container_of by, essentially,
template <class Parent, class Member>
Parent* my_container_of(Member *member, Member Parent::* ptr_to_member)
{
Parent *dummy_parent = nullptr;
auto *offset_of_member = reinterpret_cast<char *>(&(dummy_parent->*ptr_to_member));
auto address_of_parent = reinterpret_cast<char *>(member) - offset_of_member;
return reinterpret_cast<Parent *>(address_of_parent);
}
to get struct foo * from a struct data_structure_node *.
In particular, the use of ptr_to_member against the null dummy_parent makes me uneasy as it seems equivalent to performing arithmetic on a null pointer, which I understand is undefined behavior (C++11 Standard 5.7.5).
[...] Unless both pointers point to elements of the same array object, or one past the last element of the array object, the behavior is undefined
Boost.Instrusive uses an approach that seems roughly equivalent to my_container_of().
I'm wondering:
is my_container_of() safe?
is there a cleaner way of achieving this that I'm missing?
You can do intrusive data structures in C++ even nicer than C. The first thing is to use inheritance. So you try this:
struct List {
List *next{nullptr};
};
struct MyFoo : List {
MyFoo * get_next() const { return next; }
};
But there you get an error that next is a List* and not a MyFoo *. To fix this you can introduce templates:
template <typename T>
struct List {
T *next{nullptr};
};
struct MyFoo : List<MyFoo> {
MyFoo * get_next() const { return next; }
};
Now your intrusive list has the right type for the next. But you are limited to one intrusive list per object. So lets extend the template a bit more:
template <typename T, typename U>
struct List {
T *next{nullptr};
};
class Siblings;
class Children;
struct MyFoo : List<MyFoo, Siblings>, List<MyFoo, Children> {
using Sibling = List<MyFoo, Siblings>;
using Child = List<MyFoo, Children>;
MyFoo * get_sibling() const { return Sibling::next; }
MyFoo * get_child() const { return Child::next; }
};
Now you can inherit as many List as you want into a class and scoping the access to access the right List. No need of any offset() or container_of macros.
Note: The Siblings and Children classes are just declarations and purely there to give the List different types. They are never defined or instantiated.
So this is my first question. I've searched the site, found something and applied the suggestions given in them but I'm still unsure if I've done it the right way.
I'm working on a template library and here's my implementation of BST class template:
template <class T>
class bstree
{
private:
struct bstnode
{
bstnode* pRight; //node to the right (greater)
bstnode* pLeft; //node to the left (lesser)
bstnode* pParent; //parent node
T mValue; //contents
};
class bstnodeiterator : public _iterator_base<T, bstree<T>>
{
public:
bstnodeiterator(bstnode* pNode = nullptr, bstree<T> pCont = nullptr)
: _mpNodePtr(pNode), _mpCont(pCont) {}
//functions from _iterator_base<>
bool is_null() const { return (_mpNodePtr == nullptr); }
const bstree<T>* get_container() const { return this->_mpCont; }
//get_pointer() is intentionally not defined.
//operators (e.g. increment, decrement, advance by, dereference, etc)
//go here!
//...
private:
friend class bstree<T>;
//member elements:
bstree<T>* _mpCont; //the container that the iterator is created by
bstnode* _mpNodePtr; //the actual pointer pointing to the bst-node of '_mpCont'
};
public:
using val = T;
using val_ref = T&;
using val_ptr = T*;
using iter = bstnodeiterator;
public:
iter begin() const;
iter end() const;
//other public member functions (e.g. insert(), remove(), etc.) go here!
//...
private:
bstnode* _mpRoot; //The root node of the BST
size_t _mSize; //The number of elements in the container (guaranteed O(1))
};
bstnodeiterator::get_container() and bstnodeiterator::is_null() are derived from iterator_base<> which is a base class of iterators for all the other containers (e.g. vector<>, fixed_list<>, map<>, etc):
template <class T, class Cont>
struct _iterator_base
{
virtual bool is_null() const = 0;
virtual const Cont* get_container() const = 0;
/*virtual*/ const T* get_pointer() const /* = 0*/;
};
//is_null() and get_container() should be defined in derived classes
//because they are used everywhere in the library!
All three functions above are needed to be defined because they are used in everywhere else in the entire library (e.g. in algorithms, in iterator_helper class, etc).
Since a BST is a container of sorted elements, the contents of a node should not be changed dynamically. Because this will break the sorted structure of the tree. Thus, I want to prevent the programmer to use get_pointer(). Even if it returns a const pointer to the contents it can still be changed via member functions of T (For example, if T is a std::string then the contents can be changed via std::string::assign()) and I don't want this.
So, I made the function _iterator_base<*,*>::get_pointer() non-virtual in the base class. And it's not defined in the derived class, bstnodeiterator. So, if the programmer calls it from the derived class ...
bstree<std::string> strTree = { "a string", "another string", "yet another string", "test string" };
//inserted some other elements
bstree<std::string>::iterator it = strTree.begin();
//*it = "something else"; --> this won't work, because read-only dereferencing is allowed in the class.
it.get_pointer()->assign("something else"); //this will break the tree.
... then the compiler will give a linkage error: unresolved external symbol " ... ::get_pointer()".
Is this the correct way? What do you think?
EDIT:
I've just tried dereferencing and modifying:
bstree<std::string> strTree =
{
"a string",
"another string",
"yet another string",
"test string"
};
bstree<std::string>::iter it = strTree.begin();
(*it).assign("modified string"); // ----> error!
std::string pB0 = strTree.begin(); // ----> error
const std::string pB = strTree.begin();
pB->assign("modified string"); // ----> error!
...and it didn't compile. But if I change the last line with following:
it.get_pointer()->assign("modified string");
... it compiles without errors, runs, and works!
EDIT 2:
I've finally found the root of the problem: typedefs.
I didn't show the typedefs in the original question to make it look simpler and easier to read. In the original code, there is a using val_ptr = T*; under the scope of bstree<> and I'm using this typedef under the scope of bstnodeiterator:
template <class T>
class bstree
{
public:
using val = T;
using val_ref = T&;
using val_ptr = T*;
private:
class bstnodeiterator : public _iterator_base<T, bstree<T>>
{
//c'tor comes here!
const val_ptr get_pointer() { return (_mPtr ? &_mPtr->_mVal : nullptr); }
//...
};
//...
};
If I define the function as given above then I can call std::string::assign() from the returning pointer of get_pointer(). However, if I change the function's return type to const val* then I can't call string::assign().
I've finally realized that these two types are different. Probably the compiler puts the const somewhere else.
In response to OP's second edit:
Aliases are not like macros.
If you write using PtrType = T*, then const PtrType is actually
equivalent to T* const, which is a const pointer to a T object, not a pointer to a const T object. When aliases are used, further cv-qualifiers are always added on the top level. It is intuitive - if PtrType is a pointer to T, then const PtrType should be a const pointer to T.
As per the question, if you don't want users to call a virtual function, make it protected, so derived classes can implement it, but outside users cannot call it.
It is likely that you made the return type of bstnodeiterator::get_pointer() T* (instead of const T*).
You may be experiencing the pitfall of c++ covariant return types.
Both types are pointers or references (lvalue or rvalue) to classes. Multi-level pointers or references are not allowed.
The referenced/pointed-to class in the return type of Base::f() must be a unambiguous and accessible direct or indirect base class of (or is the same as) the
referenced/pointed-to class of the return type of Derived::f().
The return type of Derived::f() must be equally or less cv-qualified than the return type of Base::f().
Note: c++ reference does not have the "(or is the same as)" clause, but it is added to be coherent with the standard"
So std::string* is a valid return type if the function is overriding a function with return type const std::string*.
Consider this example:
#include <string>
std::string s = "Hello, world";
struct Base {
virtual const std::string* foo() = 0;
};
struct Derived : Base {
std::string* foo() override {
return &s;
}
};
int main() {
Derived d;
d.foo()->assign("You can do this.");
return 0;
}
The above code compiles: you can modify the string pointed by d.foo() because it returns a std::string*.
I'm trying to design a class template where its constructor has a variadic constructor and each parameter type must be the same data type. I would then like to store all parameters into a std::vector< std::shared_ptr<T> > container. If no parameters are passed in then the very first shared_ptr<> in the vector<> will be set and stored as a nullptr. Also I would like to be able to keep track of how many parameters are passed in. So far this is what I have tried.
declaration
template<typename T>
class Nodes {
private:
unsigned m_numParams;
std::vector<sstd::shared_ptr<T>> m_vNodes;
public:
explicit Nodes( T... ); // Not sure how to set first parameter as being default to nullptr along with the variadic syntax.
};
definition
template<typename T>
Nodes<T>::Nodes( T... ) {
if ( /* no parameters passed in or 1st parameter is nullptr */ ) {
T* ptr = nullptr;
m_vNodes.push_back( ptr );
} else {
for each ( /* parameter with same data type */ ) {
std::shared_ptr<T> sptr( new T() );
m_vNodes.push_back( sptr );
}
}
m_numParams = m_vNodes.size();
}
My understanding of basic templates is decent, but my use of variadic functions or templates are limited and I'm trying to learn new methods to further improve my overall skill set within the c++ language. I would like to know if my initial approach is logically the correct path; if this concept is possible to achieve and if so, what would be the most efficient way to implement this so when I use this class template this is what I would be expecting to and to actually have happen as in this example case below:
#include "SomeHeader.h"
int main() {
int x = 3;
int y = 5;
int z = 7;
Nodes<int> someNodes( x, y, z );
return 0;
}
The internal nodes private member variable would have shared_ptr<int> where
Nodes::m_vNodes[0] = address of x with value 3 stored
Nodes::m_vNodes[1] = address of y with value 5 stored
Nodes::m_vNodes[2] = address of z with value 7 stored
and if no parameters are passed as in...
#include "SomeHeader.h"
int main() {
Nodes<int> someNodes();
return 0;
}
the internal member variable would have a ''nullptr` stored in the vector[0] location. Would I need to also implement its default constructor Nodes(); Or is this achievable all from a single constructor?
Once I have this constructor properly working then I would have no problem moving forward to write the functions or methods to add, remove, retrieve, store, arrange or sort the data, compare the data, or manipulate the data. I've been searching here and there and haven't seem to find anything relevant to what I'm looking for. Any tips, advice, help, links or examples would be truly appreciated and I kindly thank you in advance.
initializer_list seems a better choice, but with variadic template, it should be something like:
template<typename T>
class Nodes {
private:
unsigned m_numParams;
std::vector<std::shared_ptr<T>> m_vNodes;
public:
Nodes() : m_numParams(0), m_vNodes{nullptr} {}
template <typename ... Ts>
explicit Nodes( Ts&&...ts) :
m_numParams(sizeof...(Ts)),
m_vNodes{std::make_shared<T>(std::forward<Ts>(ts))...}
{}
};
with initializer_list:
explicit Nodes(std::initializer_list<T> ini) :
m_numParams(ini.size())
{
for (auto&& e : ini) {
m_vNodes.push_back(std::make_shared<T>(e));
}
}
Demo
I am creating a class which interops with some Windows API code, now one of the pointers I have to initialize is done by calling a native function which initializes it.
My pointers are of type std::unique_ptr with a custom deleter, which calls the WinAPI deleter function provided, however I cannot pass the unique_ptr with the & address-of operator to the init-function. Why?
I have created a sample that demonstrates my problem:
#include <memory>
struct foo
{
int x;
};
struct custom_deleter {};
void init_foo(foo** init)
{
*init = new foo();
}
int main()
{
std::unique_ptr<foo, custom_deleter> foo_ptr;
init_foo(&foo_ptr);
}
The compiler barks and says:
source.cpp: In function 'int main()':
source.cpp:19:21: error: cannot convert 'std::unique_ptr<foo, custom_deleter>*' to 'foo**' for argument '1' to 'void init_foo(foo**)'
Somewhere under the covers, unique_ptr<foo> has a data member of type foo*.
However, it's not legitimate for a user of the class to directly modify that data member. Doing so would not necessarily preserve the class invariants of unique_ptr, in particular it wouldn't free the old pointer value (if any). In your special case you don't need that to happen, because the previous value is 0, but in general it should happen.
For that reason unique_ptr doesn't provide access to the data member, only to a copy of its value (via get() and operator->). You can't get a foo** out of your unique_ptr.
You could instead write:
foo *tmp;
init_foo(&tmp);
std::unique_ptr<foo, custom_deleter> foo_ptr(tmp);
This is exception-safe for the same reason that std::unique_ptr<foo, custom_deleter> foo_ptr(new foo()); is exception-safe: unique_ptr guarantees that whatever you pass in to its constructor will eventually get deleted using the deleter.
Btw, doesn't custom_deleter need an operator()(foo*)? Or have I missed something?
Steve has already explained what the technical problem is, however, the underlying problem goes much deeper: The code employs an idiom helpful when you deal with naked pointers. Why does this code do two-step initialization (first create the object, then initialize it) in the first place? Since you want to use smart pointers, I'd suggest you carefully adapt the code:
foo* init_foo()
{
return new foo();
}
int main()
{
std::unique_ptr<foo, custom_deleter> foo_ptr( init_foo() );
}
Of course, renaming init_foo() to create_foo() and having it return a std::unique_ptr<foo> directly would be better. Also, when you use two-step initialization, it's often advisable to consider using a class to wrap the data.
You can use the following trick:
template<class T>
class ptr_setter
{
public:
ptr_setter(T& Ptr): m_Ptr{Ptr} {}
~ptr_setter() { m_Ptr.reset(m_RawPtr); }
ptr_setter(const ptr_setter&) = delete;
ptr_setter& operator=(const ptr_setter&) = delete;
auto operator&() { return &m_RawPtr; }
private:
T& m_Ptr;
typename T::pointer m_RawPtr{};
};
// Macro will not be needed with C++17 class template deduction.
// If you dislike macros (as all normal people should)
// it's possible to replace it with a helper function,
// although this would make the code a little more complex.
#define ptr_setter(ptr) ptr_setter<decltype(ptr)>(ptr)
and then:
std::unique_ptr<foo, custom_deleter> foo_ptr;
init_foo(&ptr_setter(foo_ptr));
I eventually came up with an approach that allows to initialise unique_ptr's with a code like this:
struct TOpenSSLDeleter { ... }; // Your custom deleter
std::unique_ptr<EVP_MD_CTX, TOpenSSLDeleter> Ctx;
...
Ctx = MakeUnique(EVP_MD_CTX_create()); // MakeUnique() accepts raw pointer
And here is the solution:
template <class X>
struct TUniquePtrInitHelper {
TUniquePtrInitHelper(X *Raw) noexcept {
m_Raw = Raw;
}
template <class T, class D>
operator std::unique_ptr<T, D>() const noexcept {
return std::unique_ptr<T, D>(m_Raw);
}
private:
X *m_Raw;
};
template <class X>
TUniquePtrInitHelper<X> MakeUnique(X *Raw) noexcept {
return {Raw};
}
I have an auto pointer class and in the constructor I am passing in a pointer. I want to be able to separate new from new[] in the constructor so that I can properly call delete or delete[] in the destructor. Can this be done through template specialization? I don't want to have to pass in a boolean in the constructor.
template <typename T>
class MyAutoPtr
{
public:
MyAutoPtr(T* aPtr);
};
// in use:
MyAutoPtr<int> ptr(new int);
MyAutoPtr<int> ptr2(new int[10]);
Unfortunately, no. Both return the same type, T*. Consider using builder functions that call an appropriate overloaded constructor:
template <typename T>
class MyAutoPtr
{
public:
MyAutoPtr(T* aPtr, bool array = false);
};
template <typename T>
MyAutoPtr<T> make_ptr() {
return MyAutoPtr<T>(new T(), false);
}
template <typename T>
MyAutoPtr<T> make_ptr(size_t size) {
return MyAutoPtr<T>(new T[size], true);
}
Now you can instantiate objects as follows:
MyAutoPtr<int> ptr = make_ptr<int>();
MyAutoPtr<int> ptr2 = make_ptr<int>(10);
std::unique_ptr in C++0x will have a specialization for dynamic arrays, somewhat like shown below. However, it will be the user's task to instantiate an appropriate instance. At language level there is no way to distinguish one pointer from another.
template <class T>
class pointer
{
T* p;
public:
pointer(T* ptr = 0): p(ptr) {}
~pointer() { delete p; }
//... rest of pointer interface
};
template <class T>
class pointer<T[]>
{
T* p;
public:
pointer(T* ptr = 0): p(ptr) {}
~pointer() { delete [] p; }
//... rest of pointer and array interface
};
int main()
{
pointer<int> single(new int);
pointer<int[]> array(new int[10]);
}
Furthermore, it might not be that good to load one class with so various tasks. For example, boost has shared_ptr and shared_array.
On the other hand, you could use a specific make function.
template <class T>
MyAutoPtr<T> make();
template <class T>
MyAutoPtr<T> make(size_t n);
Of course, this means that you have the appropriate logic behind, but it's encapsulated. You can also add overload taking a T to copy the object passed into the pointer newly created etc...
Finally, it can also be done with overloads of the constructor... the point is not to call the new outside.
I think the real solution is to get rid of your own autopointer class and to get rid of the use of C-style arrays. I know this has been said many, many times before, but there really isn't much point in using C-style arrays any more. Just about everything you can do with them can be done using std::vector or with boost::array. And both of these create distinct types, so you can overload on them.
It is not possible since new int[X] yields a pointer to the initial element of the array. It has the same type as int*.
One of the common solutions is to use deleters. Add one more template argument to your class so you could pass custom deleter for your pointer. It'll make your class more universal. You could create default deleter like the following:
struct default_deleter
{
template<typename T>
void operator()( T* aPtr ) { delete aPtr; }
};
And for arrays you could pass custom deleter:
struct array_deleter
{
template<typename T>
void operator()( T* aPtr ) { delete[] aPtr; }
};
The simplest implementation will be:
template <typename T, typename D>
class MyAutoPtr
{
public:
MyAutoPtr(T* aPtr, D deleter = default_deleter() ) : ptr_(aPtr), deleter_(deleter) {};
~MyAutoPtr() { deleter_(ptr_); }
protected:
D deleter_;
T* ptr_;
};
Then you could use it as follows:
MyAutoPtr<int, array_deleter> ptr2(new int[10], array_deleter() );
You could make your class more complex so it could deduce type for deleter.
new[] is specifically defined to have pointer value despite the array-to-pointer implicit conversion that would kick in anyway.
But I don't think you're out of luck. After all, your example isn't managing a pointer to an int, it's managing a pointer to an int[10]. So the ideal way is
MyAutoPtr<int[10]> ptr2(new int[10]);
As Red-Nosed Unicorn mentions, new int[10] does not create a C-style array. It will if your compiler complies to the C standard as well, but C++ allows C-style arrays to be more than C-style arrays in C. Anyway, new will create you a C-style array if you ask like this:
MyAutoPtr<int[10]> ptr2(new int [1] [10]);
Unfortunately, delete contents; will not work even with int (*contents)[10];. The compiler is allowed to do the right thing: the standard doesn't specify that the array is converted to a pointer as with new, and I believe I recall GCC substituting delete[] and emitting a warning. But it's undefined behavior.
So, you will need two destructors, one to call delete and one to call delete[]. Since you can't partially specialize a function, the functionality demands a partially specialized helper
template< class T > struct smartptr_dtor {
void operator()( T *ptr ) { delete ptr; }
};
template< class T, size_t N > struct smartptr_dtor< T[N] > {
void operator()( T (*ptr) [N] ) { delete [] ptr; }
};
template< class T >
void proper_delete( T *p ) {
smartptr_dtor< T >()( p );
}
which for some reason I just subjected myself to ;v)
Unfortunately, this doesn't work with dynamic-sized arrays, so I'm going to write up another answer.
Second attempt…
It's quite easy to make a smart pointer class smart about arrays. As you suspected, you don't need a runtime flag or argument to the constructor if you know it's an array to begin with. The only problem is that new and new[] have identical return types, so they cannot pass this information to the smart pointer class.
template< class T, bool is_array = false >
struct smartptr {
T *storage;
smartptr( T *in_st ) : storage( in_st ) {}
~smartptr() {
if ( is_array ) delete [] storage; // one of these
else delete storage; // is dead code, optimized out
}
};
smartptr< int > sp( new int );
smartptr< int, true > sp2( new int[5] );
An alternative to the bool flag is to overload the meaning of T[] as Visitor mentions std::unique_ptr does in C++0x.
template< class T >
struct smartptr {
T *storage;
smartptr( T *in_st ) : storage( in_st ) {}
~smartptr() { delete storage; }
};
template< class T > // partial specialization
struct smartptr< T [] > {
T *storage; // "T[]" has nothing to do with storage or anything else
smartptr( T *in_st ) : storage( in_st ) {}
~smartptr() { delete [] storage; }
};
smartptr< int > sp( new int );
smartptr< int[] > sp2( new int[5] );