Deriving classes from smart pointers ?

My question is about deriving classes from smart pointers. I haven't come across examples of code doing that even though it seems to me like a nifty solution in some situations. However I am not an expert c++ programmer so I am wondering if I am missing something.

The context is that I was translating a library from a garbage collected language. I wanted to keep the ease of use while avoiding memory leaks. The library deals with various geometric objects that can be transformed or combined. All objects are immutable, i.e. rotating a object creates a new object. Complex objects can refer to less complex ones, i.e. a segment is defined by two points, but there is never a cycle.

Here is sample of the code. Does it manage memory adequately, can it be done more efficiently?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <memory>
#include <iostream>

class iPoint{
  public:
  double x,y;
  iPoint(double x, double y):x(x),y(y){
  }
};

typedef std::shared_ptr<iPoint>  pPoint;

class Point:public pPoint{
  public:
   Point(const  double x,const double y): pPoint(new iPoint(x,y)){ }
   
   double x(){ return get()->x;}
   
   double y(){return get()->y;}
   
   Point operator+(Point other){
           return Point(x() + other.x(), y() + other.y());
   }
};
   
   
class iSegment{
public:
  Point p1,p2;
  
  iSegment(Point p1, Point p2):p1(p1),p2(p2){}
};


typedef std::shared_ptr<iSegment>  pSegment;


class Segment:public pSegment{
   public:
        Segment(Point p1,Point p2) : pSegment(new iSegment(p1,p2)){}

        Point p1(){
            return get()->p1;
        }
        
        Point p2(){
            return get()->p2;
        }
        
        Segment operator+(Point p){
            return Segment(p1()+p,p2()+p);
        }
};

int main(){
           
    Point p = Point(10,50) + Point(11,55);
    Segment s = Segment(p,Point(10,34)) + Point(10,10);
    std::cout << s.p1().x() << std::endl;
}
Last edited on
In your code sample it does not make sense to inherit from a shared_ptr. It is actuall much simplier without:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

class Point{
  public:
  double x,y;
  Point(double x, double y):x(x),y(y){}
   
   Point operator+(Point other){
           return Point(x + other.x, y + other.y);
   }
};
   
   
class Segment{
public:
  Point p1,p2;
  
  Segment(Point p1, Point p2):p1(p1),p2(p2){}
        
        Segment operator+(Point p){
            return Segment(p1+p,p2+p);
        }
};

int main(){
           
    Point p = Point(10,50) + Point(11,55);
    Segment s = Segment(p,Point(10,34)) + Point(10,10);
    std::cout << s.p1.x << std::endl;
}

So I don't see why you would want to use shared_ptr.
Last edited on
Yes, you are correct, smart pointers are not a necessity and your code works perfectly. The only concern is that that the arguments are passed by value. So every function call and every return create new copies of the objects (unless I misunderstand how C++ works?). As the objects become more complex and increase in number (many thousands), that means a lot of copying and a big increase in memory usage (i.e. every instance of a segment would have its own copy of the points that define it). I am programming for a micro controller device so memory and speed that matters. Moreover, my complete code delays the calculation of some secondary characteristics until needed such as the polar coordinates of a point or the area of a polygon; without pointers many copies of the same object might exist and the same characteristics might have to be recalculated many times.

Some benefits of the pointers can be achieved with reference passing (in and out) but there is still a lot of copying happening and every object needs to be stored in a variable before being used so an expression such as the following generates a memory fault:

[code]
Point a = Point(1,2) + Point(23,34) ;
[\code]

This being said, I am not a c++ expert so i might be wrong and would be glad if i could without all this pointer business.
Last edited on
shared_ptr is not garbage collection. The problem is that it needs to be threadsafe. I.e. there is some locking required. Hence copying in some cases even be faster.

Copying is actually fast, so that you don't need to worry to much about it. However instead of a copy you can pass a const reference:
1
2
3
4
5
6
7
8
9
10
class Segment{
public:
  Point p1,p2;
  
  Segment(Point p1, Point p2):p1(p1),p2(p2){}
        
        Segment operator+(const Point &p){ // Note: No copy, most likely a pointer is passed
            return Segment(p1+p,p2+p);
        }
};


without pointers many copies of the same object might exist and the same characteristics might have to be recalculated many times.
This is not an inevitable consequence of not using pointer. It has more to do with your design of the data structures, when, where, how you store your data.

That doesn't mean you should avoid shared_ptr. Just use them when you really want to share an object.

such as the following generates a memory fault:
How so?
The context is that I was translating a library from a garbage collected language.
Be careful. In the general case, you can't directly translate garbage-collected code to C++ smart pointers (reference counting).
For example, this code leaks memory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <memory>

int instances = 0;

struct A{
    std::shared_ptr<A> link;
    A(){ instances++; }
    ~A(){ instances--; }
};

int main(){
    {
        auto a = std::make_shared<A>();
        auto b = std::make_shared<A>();
        a->link = b;
        b->link = a; //comment this to fix leak
    }
    std::cout << instances << std::endl;
}
Last edited on
@ coder777

You are probably correct and I am ready to believe that the cost of copying is very low so it could very well be that the overhead of maintaining the reference counter in the shared pointer is greater than that of duplicating the data. It also depends on how the library is used and on the kind of environment it runs on. I program for real time processing on a micro-controller so I tend to be frugal with resources.

The issue of thread-safety is indeed important and already existed in the garbage collected versions. However, since all objects are essentially immutable, it is a big issue. In reality, the objects are not truly immutable because the calculation of properties (not shown in the example) are delayed until needed and then stored for future use. For example if a point is defined in polar coordinates, its Cartesian coordinates will only be calculated if/when needed, and vice versa. Same for properties such as the area of a polygon. The necessary semaphores are easy to implement and are deadlock safe by design of the object hierarchy.

I also agree that passing constant references achieves much of the benefits of pointers. However, it requires to give up the delayed calculation and/or storage of properties as mentioned above because the objects do need to be constant. Alternatively, it is possible to use non-constant references but it implies that only variables can be used as function parameters, not complex expressions (and you are correct, there was no memory fault issue, this was the issue I ran into when I tried that approach).

In the end, I can’t argue that smart pointers are the panacea but I am finding that deriving classes from them is actually quite easy and allows for a programming style very close to what one would do in a garbage collected language. I am surprised however that I haven’t come across any example of code using them is that way so I am assuming there is an hidden flaw somewhere.
@ helios

Thanks for pointing this out.

This is the circular reference problem. In this case, it is not a concern thanks to the strict object hierarchy. Complex objects refer to simpler ones and never the other way round so cycles are impossible. I read that otherwise one should use weak pointers somewhere in the cycle but I never really understood how. Is the idea that before creating a pointer, the code needs to check for cycles and make the pointer weak if a cycle is detected? This is could represent a big overhead. Especially if also some link can be deleted, so one need also to make weak pointers strong again of the cycle is broken. So, perhaps keeping a list of all objects and running a mark and sweep routine regularly might be more efficient.

Is there any other typical trap with shared pointers ?
Is the idea that before creating a pointer, the code needs to check for cycles and make the pointer weak if a cycle is detected?
No, you can't turn a weak pointer to shared or vice-versa. The pointer type is defined statically, at compile time. The way to use std::weak_ptr is that you identify the pointers that will definitely create cycles and use a weak pointer instead. For example, for the parent pointer in a tree-like structure, or the previous pointer in a doubly-linked list.

So, perhaps keeping a list of all objects and running a mark and sweep routine regularly might be more efficient.
If the object graph is complex enough that you need to consider full traversals (e.g. if the structure is an actual graph), then all internal pointers should be weak and you should keep the strong pointers in a structure at the "root".
1
2
3
4
5
6
7
8
class Graph{
    std::vector<std::unique_ptr<Node>> nodes;
    Node *head;
};

class Node{
    std::vector<Node *> edges;
};


Is there any other typical trap with shared pointers ?
Not from a correctness point of view, as long as you don't do something like
 
std::shared_ptr<T> p(other.get());
Beyond that, just keep in mind that copying and destroying shared pointers involves atomic increments and decrements, which are somewhat expensive.
CABrouwers wrote:
However, it requires to give up the delayed calculation and/or storage of properties as mentioned above because the objects do need to be constant.


I wonder, that might be a situation for using the mutable keyword, that way things can still be const, one can still change the mutable things. After all you seem to be just changing the representation of the data.

Just an idea :+)
Topic archived. No new replies allowed.