Validity of pointer returned by operator-> - c++

I'm implementing a two-dimensional array container (like boost::multi_array<T,2>, mostly for practice). In order to use double-index notation (a[i][j]), I introduced a proxy class row_view (and const_row_view but I'm not concerned about constness here) which keeps a pointer to the beginning and end of the row.
I would also like to be able to iterate over rows and over elements within a row separately:
matrix<double> m;
// fill m
for (row_view row : m) {
for (double& elem : row) {
// do something with elem
}
}
Now, the matrix<T>::iterator class (which is meant to iterate over rows) keeps a private row_view rv; internally to keep track of the row the iterator is pointing to. Naturally, iterator also implements dereferenciation functions:
for operator*(), one would usually want to return a reference. Instead, here the right thing to do seems to return a row_view by value (i.e. return a copy of the private row_view). This ensures that when the iterator is advanced, the row_view still points to the previous row. (In a way, row_view acts like a reference would).
for operator->(), I'm not so sure. I see two options:
Return a pointer to the private row_view of the iterator:
row_view* operator->() const { return &rv; }
Return a pointer to a new row_view (a copy of the private one). Because of storage lifetime, that would have to be allocated on the heap. In order to ensure clean-up, I'd wrap it in a unique_ptr:
std::unique_ptr<row_view> operator->() const {
return std::unique_ptr<row_view>(new row_view(rv));
}
Obviously, 2 is more correct. If the iterator is advanced after operator-> is called, the row_view that is pointed to in 1 will change. However, the only way I can think of where this would matter, is if the operator-> was called by its full name and the returned pointer was bound:
matrix<double>::iterator it = m.begin();
row_view* row_ptr = it.operator->();
// row_ptr points to view to first row
++it;
// in version 1: row_ptr points to second row (unintended)
// in version 2: row_ptr still points to first row (intended)
However, this is not how you'd typically use operator->. In such a use case, you'd probably call operator* and keep a reference to the first row. Usually, one would immediately use the pointer to call a member function of row_view or access a member, e.g. it->sum().
My question now is this: Given that the -> syntax suggests immediate use, is the validity of the pointer returned by operator-> considered to be limited to that situation, or would a safe implementation account for the above "abuse"?
Obviously, solution 2 is way more expensive, as it requires heap-allocation. This is of course very much undesirable, as dereferenciation is quite a common task and there is no real need for it: using operator* instead avoids these problems as it returns a stack-allocated copy of the row_view.

As you know, operator-> is applied recursively on the functions return type until a raw pointer is encountered. The only exception is when it's called by name like in your code sample.
You can use that to your advantage and return a custom proxy object. To avoid the scenario in your last code snippet, this object needs to satisfy several requirements:
Its type name should be private to the matrix<>::iterator, so outside code could not refer to it.
Its construction/copy/assignment should be private. matrix<>::iterator will have access to those by virtue of being a friend.
An implementation will look somewhat like this:
template <...>
class matrix<...>::iterator {
private:
class row_proxy {
row_view *rv_;
friend class iterator;
row_proxy(row_view *rv) : rv_(rv) {}
row_proxy(row_proxy const&) = default;
row_proxy& operator=(row_proxy const&) = default;
public:
row_view* operator->() { return rv_; }
};
public:
row_proxy operator->() {
row_proxy ret(/*some row view*/);
return ret;
}
};
The implementation of operator-> returns a named object to avoid any loopholes due to guaranteed copy elision in C++17. Code that use the operator inline (it->mem) will work as before. However, any attempt to call operator->() by name without discarding the return value, will not compile.
Live Example
struct data {
int a;
int b;
} stat;
class iterator {
private:
class proxy {
data *d_;
friend class iterator;
proxy(data *d) : d_(d) {}
proxy(proxy const&) = default;
proxy& operator=(proxy const&) = default;
public:
data* operator->() { return d_; }
};
public:
proxy operator->() {
proxy ret(&stat);
return ret;
}
};
int main()
{
iterator i;
i->a = 3;
// All the following will not compile
// iterator::proxy p = i.operator->();
// auto p = i.operator->();
// auto p{i.operator->()};
}
Upon further review of my suggested solution, I realized that it's not quite as fool-proof as I thought. One cannot create an object of the proxy class outside the scope of iterator, but one can still bind a reference to it:
auto &&r = i.operator->();
auto *d = r.operator->();
Thus allowing to apply operator->() again.
The immediate solution is to qualify the operator of the proxy object, and make it applicable only to rvalues. Like so for my live example:
data* operator->() && { return d_; }
This will cause the two lines above to emit an error again, while the proper use of the iterator still works. Unfortunately, this still doesn't protect the API from abuse, due to the availability of casting, mainly:
auto &&r = i.operator->();
auto *d = std::move(r).operator->();
Which is a death blow to the whole endeavor. There is no preventing this.
So in conclusion, there is no protection from a direction call to operator-> on the iterator object. At the most, we can only make the API really hard to use incorrectly, while the correct usage remains easy.
If creation of row_view copies is expansive, this may be good enough. But that is for you to consider.
Another point for consideration, which I haven't touched on in this answer, is that the proxy could be used to implement copy on write. But that class could be just as vulnerable as the proxy in my answer, unless great care is taken and fairly conservative design is used.

Related

How to return a private pointer to a list of pointers as const?

I have a pointer to a list of pointers, as a private variable. I also have a getter that returns the pointer to the list. I need to protect it from changes.
I couldn't find how to use reinterpret_cast or const_cast on this.
class typeA{
shared_ptr<list<shared_ptr<typeB>>> l;
public:
shared_ptr<list<shared_ptr<const typeB>>> getList(){return (l);};
};
The compiler returns:
error: could not convert ‘((typeA*)this)->typeA::x’ from ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<typeB> > >’ to ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<const typeB> > >’|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|
It seems as const shared_ptr<list<shared_ptr<typeB>>> and shared_ptr<const list<shared_ptr<typeB>>> work fine.
Is it possible to do return l as a complete const, like:
const shared_ptr<const list<shared_ptr<const typeB>>>
or at least like:
shared_ptr<list<shared_ptr<const typeB>>>
?
References instead of pointers is not an option. To declare l as shared_ptr<list<shared_ptr<const typeB>>> also is not a wanted solution.
EDIT: no 'int' anymore.
It seems as it is not possible exactly what I wanted, but the suggested solutions are good. Yes, copying pointers is acceptable.
My bad i didn't put typeB immediately. I am aware of some advantages of references over pointers, but I hoped there is some similar solution.
You can create a new list of const int's from your original list and return that:
std::shared_ptr<std::list<std::shared_ptr<const int>>> getList(){
return std::make_shared<std::list<std::shared_ptr<const int>>>(l->begin(), l->end());
}
If you want to prevent people from making changes to the returned list, make it const too:
std::shared_ptr<const std::list<std::shared_ptr<const T>>> getList(){
return std::make_shared<const std::list<std::shared_ptr<const T>>>(l->cbegin(), l->cend());
}
The shared pointer returned by this function does not point to the original list but to the newly created list.
An alternative may be to provide iterators that, when dereferenced, returns const T& (where T is the type you actually store). That way there will be no need to copy the whole list every time you want to go though it. Example:
#include <iostream>
#include <list>
#include <memory>
struct example {
int data;
example(int x) : data(x) {}
};
template <class T>
class typeA {
std::shared_ptr<std::list<std::shared_ptr<T>>> l = std::make_shared<std::list<std::shared_ptr<T>>>();
public:
template< class... Args >
void add( Args&&... args ) {
l->emplace_back(std::make_shared<T>(std::forward<Args>(args)...));
}
// a very basic iterator that can be extended as needed
struct const_iterator {
using uiterator = typename std::list<std::shared_ptr<T>>::const_iterator;
uiterator lit;
const_iterator(uiterator init) : lit(init) {}
const_iterator& operator++() { ++lit; return *this; }
const T& operator*() const { return *(*lit).get(); }
bool operator!=(const const_iterator& rhs) const { return lit != rhs.lit; }
};
const_iterator cbegin() const noexcept { return const_iterator(l->cbegin()); }
const_iterator cend() const noexcept { return const_iterator(l->cend()); }
auto begin() const noexcept { return cbegin(); }
auto end() const noexcept { return cend(); }
};
int main() {
typeA<example> apa;
apa.add(10);
apa.add(20);
apa.add(30);
for(auto& a : apa) {
// a.data = 5; // error: assignment of member ‘example::data’ in read-only object
std::cout << a.data << "\n";
}
}
When you convert a pointer-to-nonconst to a pointer-to-const, you have two pointers. Furthermore, a list of pointers-to-nonconst is a completely different type from a list of pointers-to-const.
Thus, if you want to return a pointer to a list of pointers-to-const, what you must have is a list of pointers-to-const. But you don't have such list. You have a list of pointers-to-nonconst and those list types are not interconvertible.
Of course, you could transform your pointers-to-nonconst into a list of pointers-to-const, but you must understand that it is a separate list. A pointer to former type cannot point to the latter.
So, here is an example to transform the list (I didn't test, may contain typos or mistakes):
list<shared_ptr<const int>> const_copy_of_list;
std::transform(l->begin(), l->end(), std::back_inserter(const_copy_of_list),
[](auto& ptr) {
return static_pointer_cast<const int>(ptr);
});
// or more simply as shown by Ted:
list<shared_ptr<const int>> const_copy_of_list(l->begin(), l->end());
Since we have created a completely new list, which cannot be pointed by l, it makes little sense to return a pointer. Let us return the list itself. The caller can wrap the list in shared ownership if the need it, but don't have to when it is against their needs:
list<shared_ptr<const int>> getConstCopyList() {
// ... the transorm above
return const_copy_of_list;
}
Note that while the list is separate, the pointers inside still point to the same integers.
As a side note, please consider whether shared ownership of an int object makes sense for your program - I'm assuming it is a simplification for the example.
Also reconsider whether "References instead of pointers is not an option" is a sensible requirement.
You problem squarely lies at
but I do not want to mix references and pointers. It is easier and cleaner to have just pointers.
What you are finding here is that statement is wrong. A list<TypeB> can bind a const list<TypeB> & reference, and none of the list's members will allow any modification of the TypeB objects.
class typeA {
std::vector<typeB> l;
public:
const std::vector<typeB> & getList() const { return l; };
};
If you really really must have const typeB, you could instead return a projection of l that has added const, but that wouldn't be a Container, but instead a Range (using the ranges library voted into C++20, see also its standalone implementation)
std::shared_ptr<const typeB> add_const(std::shared_ptr<typeB> ptr)
{
return { ptr, ptr.get() };
}
class typeA {
std::vector<std::shared_ptr<typeB>> l;
public:
auto getList() const { return l | std::ranges::transform(add_const); };
};
Another alternative is that you can wrap your std::shared_ptrs in something like std::experimental::propagate_const, and just directly return them.
What you have here is a VERY complex construct:
shared_ptr<list<shared_ptr<typeB>>> l;
This is three levels of indirection, of which two have reference counting lifetime management, and the third is a container (and not memory-contiguous at that).
Naturally, given this complex structure, it is not going to be easy to convert it to another type:
shared_ptr<list<shared_ptr<const typeB>>>
Notice that std::list<A> and std::list<const A> are two distinct types by design of standard library. When you want to pass around non-modifying handles to your containers, you are usually supposed to use const_iterators.
In your case there is a shared_ptr on top of the list, so you can't use iterators if you want that reference counting behavior.
At this point comes the question: do you REALLY want that behavior?
Are you expecting a situation where your typeA instance is destroyed, but you still have some other typeA instances with the same container?
Are you expecting a situation where all your typeA instances sharing the container are destroyed, but you still have some references to that container in other places of your runtime?
Are you expecting a situation where the container itself is destroyed, but you still have some references to some of the elements?
Do you have any reason at all to use std::list instead of more conventional containers to store shared pointers?
If you answer YES to all the bullet points, then to achieve your goal you'll probably have to design a new class that would behave as a holder for your shared_ptr<list<shared_ptr<typeB>>>, while only providing const access to the elements.
If, however, on one of the bullet points your answer is NO, consider redesigning the l type. I suggest starting with std::vector<typeB> and then only adding necessary modifications one by one.
The problem with templates is that for any
template <typename T>
class C { };
any two pairs C<TypeA> and C<TypeB> are totally unrelated classes – this is even the case if TypeA and TypeB only differ in const-ness.
So what you actually want to have is technically not possible. I won't present a new workaround for now, as there are already, but try to look a bit further: As denoted in comments already, you might be facing a XY problem.
Question is: What would a user do with such a list? She/he might be iterating over it – or access single elements. Then why not make your entire class look/behave like a list?
class typeA
{
// wondering pretty much why you need a shared pointer here at all!
// (instead of directly aggregating the list)
shared_ptr<list<shared_ptr<typeB>>> l;
public:
shared_ptr<list<shared_ptr<typeB>>>::const_iterator begin() { return l->begin(); }
shared_ptr<list<shared_ptr<typeB>>>::const_iterator end() { return l->end(); }
};
If you used a vector instead of a list, I'd yet provide an index operator:
shared_ptr<typeB /* const or not? */> operator[](size_t index);
Now one problem yet remains unsolved so far: The two const_iterators returned have an immutable shared pointer, but the pointee is still mutable!
This is a bit of trouble - you'll need to implement your own iterator class now:
class TypeA
{
public:
class iterator
{
std::list<std::shared_ptr<int>>::iterator i;
public:
// implementation as needed: operators, type traits, etc.
};
};
Have a look at std::iterator for a full example – be aware, though, that std::iterator is deprecated, so you'll need to implement the type-traits yourself.
The iterator tag to be used would be std::bidirectional_iterator_tag or random_access_iterator_tag (contiguous_iterator_tag with C++20), if you use a std::vector inside.
Now important is how you implement two of the needed operators:
std::shared_ptr<int const> TypeA::iterator::operator*()
{
return std::shared_ptr<int const>(*i);
}
std::shared_ptr<int const> TypeA::iterator::operator->()
{
return *this;
}
The other operators would just forward the operation to the internal iterators (increment, decrement if available, comparison, etc).
I do not claim this is the Holy Grail, the path you need to follow under all circumstances. But it is a valuable alternative worth to at least consider...

Copy on write proper usage?

I'm tyring to understand how COW works, I found following class on wikibooks, but I don't understand this code.
template <class T>
class CowPtr
{
public:
typedef boost::shared_ptr<T> RefPtr;
private:
RefPtr m_sp;
void detach()
{
T* tmp = m_sp.get();
if( !( tmp == 0 || m_sp.unique() ) ) {
m_sp = RefPtr( new T( *tmp ) );
}
}
public:
CowPtr(T* t)
: m_sp(t)
{}
CowPtr(const RefPtr& refptr)
: m_sp(refptr)
{}
CowPtr(const CowPtr& cowptr)
: m_sp(cowptr.m_sp)
{}
CowPtr& operator=(const CowPtr& rhs)
{
m_sp = rhs.m_sp; // no need to check for self-assignment with boost::shared_ptr
return *this;
}
const T& operator*() const
{
return *m_sp;
}
T& operator*()
{
detach();
return *m_sp;
}
const T* operator->() const
{
return m_sp.operator->();
}
T* operator->()
{
detach();
return m_sp.operator->();
}
};
And I would use it in my multithreaded application on map object, which is shared.
map<unsigned int, LPOBJECT> map;
So I've assigned it to template and now I have :
CowPtr<map<unsigned int, LPOBJECT>> map;
And now my questions :
How I should propertly take instance of the map for random thread which want only read map objects ?
How I should modify map object from random thread, for ex. insert new object or erase it ?
The code you post is poor to the point of being unusable; the
author doesn't seem to understand how const works in C++.
Practically speaking: CoW requires some knowledge of the
operations being done on the class. The CoW wrapper has to
trigger the copy when an operation on the wrapped object might
modify; in cases where the wrapped object can "leak" pointers
or iterators which allow modification, it also has to be able to
memorize this, to require deep copy once anything has been
leaked. The code you posted triggers the copy depending on
whether the pointer is const or not, which isn't at all the same
thing. Thus, with an std::map, calling std::map<>::find on
the map should not trigger copy on write, even if the pointer
is not const, and calling std::map<>::insert should, even if
the pointer is const.
With regards to threading: it is very difficult to make a CoW
class thread safe without grabbing a lock for every operation
which may mutate, because it's very difficult to know when
the actual objects are shared between threads. And it's even
more difficult if the object allows pointers or iterators to
leak, as do the standard library objects.
You don't explain why you want a thread-safe CoW map. What's
the point of the map if each time you add or remove an element,
you end up with a new copy, which isn't visible in other
instances? If it's just to start individual instances with
a copy of some existing map, std::map has a copy constructor
which does the job just fine, and you don't need any fancy
wrapper.
How does this work?
The class class CowPtr does hold a shared pointer to the underlying object. It does have a private method to copy construct a new object and assign the pointer to to the local shared pointer (if any other object does hold a reference to it): void detach().
The relevant part of this code is, that it has each method as
const return_type&
method_name() const
and once without const. The const after a method guarantees that the method does not modify the object, the method is called a const method. As the reference to the underlying object is const too, that method is being called every time you require a reference without modifying it.
If however you chose to modify the Object behind the reference, for example:
CowPtr<std::map<unsigned int, LPOBJECT>> map;
map->clear();
the non-const method T& operator->() is being called, which calls detach(). By doing so, a copy is made if any other CowPtr or shared_ptr is referencing the same underlying object (the instance of <unsigned int, LPOBJECT> in this case)
How to use it?
Just how you would use a std::shared_ptr or boost::shared_ptr. The cool thing about that implementation is that it does everything automatically.
Remarks
This is no COW though, as a copy is made even if you do not write, it is more a Copy if you do not guarantee that you do not write-Implementation.

Array Subscription: returning Reference vs proxy class method

While searching for methods for overloading Subscript('[]') operator for template class, I came across two different techniques.
First Technique:
Overloading the operator [] returning pointer to the container directly, which will allow both reading value and assigning value. A sample implementation of this technique:
template <class T>
class X
{
int _size;
T *container;
public:
X(int sz)
{
_size=sz;
container=new T[sz]();
}
~X()
{
}
T& operator [](int indx)
{
return container[indx];
}
};
With main() as:
X<int> sample(100);
cout<<sample[9]<<endl;
sample[9]=9;
cout<<sample[9]<<endl;
Output:
0
9
Second Technique:
Second technique involves declaring a proxy class and overloading the operator = via that class. A sample implementation of this technique:
template <class T>
class X
{
int _size;
T *container;
public:
X(int sz)
{
_size=sz;
container=new T[sz]();
}
~X()
{
}
class Proxy
{
int indx;
X<T> &parent;
public:
Proxy(X<T> &p, int x) : parent(p),indx(x)
{
}
void operator =(T assgn)
{
parent.container[indx]=assgn;
}
operator T const &()
{
return parent.container[indx];
}
friend class X<T>;//unnecessary line, I know!
};
Proxy operator[](int indx)
{
return Proxy(*this,indx);
}
};
With same main() we get same output.
I personally like the second method. But, I really want to compare these two methods. What are the major functional difference of these two techniques. What advantages each of these method have?
The proxy-based technique you describe can be used if you want to expose a sequence of elements that are not stored as-such (requiring a conversion from and to storage) or that cannot simply be accessed by-reference. An example is std::vector < bool >: it packs eight bools in every byte (one in each bit) in storage. Storing them that way, it is not possible to return a reference to a single such bool, so the index operator returns a "proxy-object" instead to support reading and writing of the contained bools.
If you can return a direct reference to the stored objects, there is no real advantage to wrapping it in a proxy unless you want to restrict the assignment (only allow positive values in the container for example).
Usually, a proxy is used when you want to return something that doesn't match the internal storage of the data. The classic example is a 2D matrix, with elements stored in a single array. If you provide an operator to return rows or columns, you need proxies. Another example is the infamous std::vector<bool>, where the data need not be stored as a block of bool, but access must return bool to the user.
Proxies can be used to return different "views" of segments of an internal data representation. In your example, there seems no reason to use them.
Getting proxy objects right for most client usage is considerably trickier... for example - what if someone says:
tcp_peer.send_from_iterator_range(&sample[2], &sample[7+1]);
If sample::operator[] returns a temporary proxy, and that proxy doesn't carefully replace operator&, then the code asks for the addresses of the proxies themselves.
Some client usage just can't be supported without losing the Proxy's ability to intercept reads and/or writes to the data, for example in...
Container::element_type& ref = container[n];
ref = 20;
...the client code assumes the container's operator[] will yield a reference to an actual element. Any proxy returned by operator[] must either provide an operator element_type&() - handing over such a reference and taking itself out of play - or refuse to (e.g. only return a const reference, return a temporary by value to which a non-const reference can't be bound) and force edits to the client code.
So, proxy's are 95% as good when you need them, but best avoided when you don't.

Non-owning holder with assignment semantics

I have a class that should hold a reference to some data, without owning that data (i.e. the actual data is guaranteed not to go out of scope). In particular, the class cannot make a copy – the data is easily several gigabytes in size.
Now, the usual implementation (I assume) is to have a reference to the data:
struct holder_ref {
type const& value;
holder_ref(type const& value) : value(value) { }
};
(Please note that the constness has absolutely no bearing on the problem).
Now, I absolutely need this class to be assignable (i.e. have a working operator =). I thought this was a fairly common problem but I can’t remember how (if ever) I’ve solved it before.
The problem is that a reference cannot be assigned and there’s simply no way around this. The only solution I’ve come up with uses placement new in place of the assignment operator:
// x = other_x; gets replaced with:
x.~T();
new (&x) T(other_x);
Now, this works and is standard compliant. But it sure is ugly. No – inacceptable.
So I’m searching for alternatives. One idea is to use pointers, but I’m unsure whether my constructor is actually guaranteed to work (and passing a pointer is impossible due to the interface I have to adhere to):
struct holder_ptr {
type const* value;
// Is this legal?
holder_ptr(type const& value = 0) : value(&value) { }
};
But I’d rather use a reference, if at all possible. Only – how to implement the assignment operator?
struct holder_ref {
type const& value;
holder_ref(type const& value = 0) : value(value) { }
holder_ref& operator =(holder_ref const& other) {
// Now what?!
return *this;
}
};
As a test case, consider the following code:
int main() {
int const TEST1 = 23;
int const TEST2 = 13;
int const TEST3 = 42;
std::vector<holder_ptr> hptr(1);
std::vector<holder_ref> href(2);
// Variant 1. Pointer.
hptr[0] = holder_ptr(TEST1);
// Variant 2. Placement new.
href[0].~holder_ref();
new (&href[0]) holder_ref(TEST2);
// Variant 3. ???
href[1] = holder_ref(TEST3);
assert(*hptr[0].value == TEST1); // Works (?)
assert(href[0].value == TEST2); // Works
assert(href[1].value == TEST3); // BOOM!
}
(Also, just to make this clear – the type we’re talking about is non-POD and I need a standard compliant solution.)
I don't see anything wrong with using a holder_ptr. It can be implemented something like so:
struct bad_holder : std::exception { };
struct holder_ptr {
holder_ptr() : value(0) { }
holder_ptr(type const& value) : value(&value) { }
type const& get() {
if (value == 0) throw bad_holder();
return *value;
}
private:
type const* value;
};
So long as you always assign to the pointer from a reference, you know that you have a valid object (that, or you ended up with a "null reference" previously, in which case you have other, bigger problems since you'll already have invoked undefined behavior).
With this solution, the interface is implemented entirely in terms of references, but under the hood a pointer is used so that the type is assignable. The use of references in the interface ensures there are none of the concerns that come with using pointers (namely, you never have to worry whether the pointer is null).
Edit: I've updated the example to allow for the holder to be default constructible.
I'd use the pointer holder. But if you are dead set against that, how about hiding your placement new operator=:
holder_ref& operator =(holder_ref const& other) {
new (this) holder_ref(other);
return *this;
}
Is a TR1 weak_ptr standard compliant enough?

Implementing Smart Pointer - Dynamic Allocation with templates

I'm in the process of writing a smart pointer countedptr and I've hit a speed bump. The basic function of countedptr is to work like any other smart pointer and also have a count of how many pointers are pointing to a single object. So far, the code is:
[SOLVED]
#include "std_lib_facilities.h"
template <class T>
class counted_ptr{
private:
T* pointer;
int* count;
public:
counted_ptr(T* p = 0, int* c = new int(1)) : pointer(p), count(c) {} // default constructor
explicit counted_ptr(const counted_ptr& p) : pointer(p.pointer), count(p.count) { ++*count; } // copy constructor
~counted_ptr() { --*count; delete pointer; }
counted_ptr& operator=(const counted_ptr& p)
{
pointer = p.pointer;
count = p.count;
++*count;
return *this;
}
T* operator->() const{ return pointer; }
T& operator*() const { return *pointer; }
int Get_count() const { return *count; }
};
int main()
{
counted_ptr<double> one;
counted_ptr<double>two(one);
int a = one.Get_count();
cout << a << endl;
}
When I try to do something like
one->pointer = new double(5);
then I get a compiler error saying "request for member 'pointer' in '*(&one)->counted_ptr::operator->with T = double' which is of non-class type double".
I considered making a function to do this, and while I could make a function to allocate an array of T's, I can't think of a way of making one for allocating actual objects. Any help is appreciated, thanks.
Old Solution
What about another assignment operator?
counted_ptr& counted_ptr::operator=(T* p)
{
if (! --*count) { delete count; }
pointer = p;
count = new int(1);
return *this;
}
...
one = new double(5);
Also, your destructor always deletes a shared pointer, which is probably what caused *one to be a random nomber. Perhaps you want something like:
counted_ptr::~counted_ptr() { if (! --*count) { delete pointer; delete count; } }
New Solution
As you want repointing a counted_ptr (eg one = new double(5)) to update all related counted_ptrs, place both the pointer and the count in a helper class, and have your pointer class hold a pointer to the helper class (you might already be headed down this path). You could go two ways in filling out this design:
Make the helper class a simple struct (and a private inner class) and place all the logic in the outer class methods
Make counted_ptr the helper class. counted_ptr maintains a reference count but doesn't automatically update the count; it's not a smart pointer, it only responds to release and retain messages. If you're at all familiar with Objective-C, this is basically its traditional memory management (autoreleasing aside). counted_ptr may or may not delete itself when the reference count reaches 0 (another potential difference from Obj-C). counted_ptrs shouldn't be copyable. The intent is that for any plain pointer, there should be at most one counted_ptr.
Create a smart_ptr class that has a pointer to a counted_ptr, which is shared among smart_ptr instances that are supposed to hold the same plain pointer. smart_ptr is responsible for automatically updating the count by sending its counted_ptr release and retain methods.
counted_ptr may or may not be a private inner class of shared_ptr.
Here's an interface for option two. Since you're doing this as an exercise, I'll let you fill out the method definitions. Potential implementations would be similar to what's already been posted except that you don't need a copy constructor and copy assignment operator for counted_ptr, counted_ptr::~counted_ptr doesn't call counted_ptr::release (that's smart_ptr::~smart_ptr's job) and counted_ptr::release might not free counted_ptr::_pointer (you might leave that up to the destructor).
// counted_ptr owns its pointer an will free it when appropriate.
template <typename T>
class counted_ptr {
private:
T *_pointer;
size_t _count;
// Make copying illegal
explicit counted_ptr(const counted_ptr&);
counted_ptr& operator=(const counted_ptr<T>& p);
public:
counted_ptr(T* p=0, size_t c=1);
~counted_ptr();
void retain(); // increase reference count.
bool release(); // decrease reference count. Return true iff count is 0
void reassign(T *p); // point to something else.
size_t count() const;
counted_ptr& operator=(T* p);
T& operator*() const;
T* operator->() const;
};
template <typename T>
class smart_ptr {
private:
counted_ptr<T> *_shared;
void release(); // release the shared pointer
void retain(); // retain the shared pointer
public:
smart_ptr(T* p=0, int c=1); // make a smart_ptr that points to p
explicit smart_ptr(counted_ptr<T>& p); // make a smart_ptr that shares p
explicit smart_ptr(smart_ptr& p); // copy constructor
~smart_ptr();
// note: a smart_ptr's brethren are the smart_ptrs that share a counted_ptr.
smart_ptr& operator=(smart_ptr& p); /* Join p's brethren. Doesn't alter pre-call
* brethren. p is non-const because this->_shared can't be const. */
smart_ptr& operator=(counted_ptr<T>& p); /* Share p. Doesn't alter brethren.
* p is non-const because *this isn't const. */
smart_ptr& operator=(T* p); // repoint this pointer. Alters brethren
size_t count() const; // reference count
T& operator*() const; // delegate these to _shared
T* operator->() const;
};
Hopefully, the only ambiguous points above are the intentional ones.
(Sorry, newbie here, and can't leave comments). What Adatapost added, "one=new double(5);" should work. One other change needed, though: the reference counting needs a little help.
...
~counted_ptr() {
--*count;
// deallocate objects whose last reference is gone.
if (!*count)
{
delete pointer;
delete count;
}
}
counted_ptr& operator=(const counted_ptr& p)
{
// be careful to accommodate self assignment
++*p.count;
// may lose a reference here
--*count;
if (!*count)
{
delete pointer;
delete count;
}
count=p.count;
pointer=p.pointer;
return *this;
}
Of course, there's some code repetition here. It might make sense to refactor that code into its own function, e.g.
private:
/** remove our reference */
void release()
{
--*count;
if (!*count)
{
delete pointer;
delete count;
}
}
Did you, perhaps, mean "one.pointer=new double(5);"? Writing "one->pointer=new double(5);" invokes counted_ptr<double>::operator->. That is, it is approximately equivalent to:
double *tmp = one.operator->(); // returns one.pointer
tmp->pointer = new double(5);
But a double pointer isn't a structure, and so it doesn't have a pointer member.
Since the immediate problem has already been solved, I want to offer something more long term:
As you continue to develop this code, you'll definitely want to offer it up for full review by experienced programmers, whether here or elsewhere. There were a few obvious problems with your code as you posted it, though outis has helped correct them. But even once your code all compiles and seems to work in your own tests, there may be tests and situations which you haven't yet learned to think about. Smart pointers can easily have subtle problems that don't show up until very specific situations. So you'll want others to look over your code to find anything which you may have missed.
Please don't take this as any kind of insult towards your current code. I'm just offering this as friendly advice to ensure you learn the most you can out of this project.
Unless you are not doing this for academic reasons, you might want to use consider using the use_count() member of boost::shared_ptr. It's not entirely efficient, but it does work and you're better off using something well tested, mature, and thread safe. If you are doing this for learning purposes, be sure to check out the treatment of Reference Counting and Smart Pointers in More Effective C++.
You need to decrement the count and possibly delete the pointer to the old value in operator = before you overwrite it. You also need 'delete count' everywhere you have 'delete pointer' to avoid leaking memory