weak_ptrは今回のケースではどんぴしゃですね
ただ循環参照の問題はきっかけではありますが本質ではなく、
端的に言うと、スマートポインタ使用時に生ポを出すのはありなのかという疑問です
>>395が一番スマートなのでしょうか
なぜか全く思いつきませんでした

shared_ptr<int> obj1::get() {
  return ptr; // shared_ptrオブジェクトを返す コスト大
} // 一時的に使用するために呼び出す場合でもshared_ptrで返すべきなのだろうか(一時的に所有すると考えるべき?)

const int * obj2::get() {
  return ptr.get(); // 生ポを返す
} // 生ポを出すのは不格好な気がする

const int * obj3::get() { // unique_ptr<int> ptr;
  return ptr.get(); // 生ポをむき出しにするのであれば、unique_ptrでいいのでは
} // もちろんshared_ptrでなければいけないケースもあるだろうけど