2

Suppose I have these classes:

struct Engine {
  int engine_data;
};

struct Car {
  shared_ptr<Engine> engine;
  int car_data;
};

For performance reasons, I want to make them tightly packed in memory (but I don't want to lose the flexibility of the design). So, I can create a "packed" structure, and a factory that will transparently return a new B instance:

struct PackedCarAndEngine {
    Engine engine;
    Car car;
};

shared_ptr<Car> new_Car() {
    shared_ptr<PackedCarAndEngine> packed = make_shared<PackedCarAndEngine>();

    // uses aliasing shared_ptr constructor
    packed->car.engine = shared_ptr<Engine>(packed, &packed->engine);

    // again
    shared_ptr<Car> car = shared_ptr<Car>(packed, &packed->car);

    return car;
}

The problem is that this "car" instance will never be destroyed, because it have a reference count of two. When it dies, it will have a reference count of one, forever. Do you know a better way to keep using the internal shared_ptr's (so that I can attribute an "unpacked" reference if I want), and still make this packed structure?

UPDATE

I could use a no-op deleter, but then it would be very dangerous if I decide to keep the engine but not the car:

    // ...
    packed->car.engine = shared_ptr<Engine>(&packed->engine, do_nothing_deleter);
    // ...

shared_ptr<Car> my_car = new_Car();
shared_ptr<Engine> my_engine = my_car->engine;
my_car.reset(); // Danger: engine was destroyed here!!!
cout << my_engine->engine_data; // Crash!
e.tadeu
  • 5,024
  • 2
  • 20
  • 21
  • 4
    Why don't you just make an Engine a straight member of Car? If you're always creating a Car and Engine as part of the same object it implies they have the same lifetime. Car knows about engine but not the other way around. Unless you're planning on sharing the same Engine instance between different Car instances wouldn't `struct Car { Engine engine; int car_data; };` be a simpler design? – CB Bailey Dec 14 '10 at 12:29
  • @Charles I've thought about this... in case I can't solve the problem easily with `shared_ptr`'s, I will fall back to this solution. In this example I gave, I have only one composition, but in my real design, I have many classes composed this way, in multiple layers, and I want to have the flexibility of assigning different `engine`s coming from other places. – e.tadeu Dec 14 '10 at 12:45

2 Answers2

1

Consider using weak_ptr instead of shared_ptr inside struct Car, it does not contribute to reference count, but can be converted to a shared_ptr when needed.

Alex B
  • 82,554
  • 44
  • 203
  • 280
  • Thanks! The problem is that I will be accessing the `engine` instance a lot, and `weak_ptr` does not have `operator->`. (I don't want to convert it to a `shared_ptr` every time I want to access an attribute or method of it). – e.tadeu Dec 14 '10 at 12:41
0
void nop(Engine*) { /* do nothing */ }

packed->car.engine = shared_ptr<Engine>(&packed->engine, nop);

Explanation: This code creates a shared_ptr that thinks that it owns the engine but in fact it has a separate reference counter AND it does nothing when the deleter is called.

Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
  • 1
    Thanks! I have thought about that before, but this may create a dangling pointer, which is much worse (see post update). – e.tadeu Dec 14 '10 at 12:33
  • 1
    @e.tadeu: then I think that shared_ptr doesn't fit your needs. Try redesigning your approach. Do you really need a pointer here? Why a regular membership doesn't work? – Yakov Galka Dec 14 '10 at 13:01
  • Yup, that's what I think. Perhaps I would need a "customized" shared_ptr, or an extension to it to cover internal data. But, anyway, I posted it here to check if there is a solution I'm missing. :) – e.tadeu Dec 14 '10 at 13:10