0

When potentially manipulating an object it is sometimes good to keep track of the previous object. In object-oriented programming that means constructing a new object with a pointer to this inside the virtual function.

Clearly that is problematic in terms of memory management, and specifically for std::shared_ptr.

One solution is to derive from std::enable_shared_from_this and use shared_from_this see how to return shared_ptr to current object from inside the "this" object itself However, to be safe we need to also hide the constructor to enforce that construction uses new, Visitor Pattern and std::shared_ptr But this means we lose performance since we cannot directly use std::make_shared, and we don't get the same benefit automatically - Does enable_shared_from_this and make_shared provide the same optimization That can be circumvented, but the per-class boiler-plate currently seems excessive: How do I call ::std::make_shared on a class with only protected or private constructors?

Note: below there is just one class B - but in practice there might be a large number of them and several foo-operations (doing similar but related things on similar and related objects). That seems to differ from some of the previous questions.

struct A : public std::enable_shared_from_this<A> {
    virtual ~A() { ; }

    std::shared_ptr<A> pred; // Previous A-object

    virtual std::shared_ptr<A> foo() { return shared_from_this(); };
    // Possibly return a pointer to a new A
protected:
    A() = default;
};

struct B : public A {
    template<typename ...args>
    static auto create(args&& ... params) {
        return std::shared_ptr<B>(new B(std::forward<args>(params)...));
    };
    int x = 0;
    virtual std::shared_ptr<A> foo() {
        std::shared_ptr<A> res = B::create(x + 1);
        res->pred=shared_from_this();
        return res;
    }
private:
    B(int x_) : x{ x_ } {; }
};

Another solution would be to have a helper function taking the shared_ptr. That works, but all calls would need to use A::foo(x) instead of x->foo() which looks somewhat odd. However, the boiler-plate is in this case common for A::foo so it can be shared between different derived classes.

struct A {
  virtual ~A() {;}

  std::shared_ptr<A> pred; // Previous A-object

  static std::shared_ptr<A> foo(std::shared_ptr<A>&f) {
   std::shared_ptr<A> result;
   if (f) result=f->fooInternal();
   if (result) result->pred=f; // Set predecessor.
   else result=f; // Return original if nothing happened.
   return result;
  }
protected:
  virtual std::shared_ptr<A> fooInternal() {return nullptr;};
  // Possibly return a pointer to a new A
  // Return nullptr to signify that nothing happened
};

struct B : public A {
   int x=0;
   B(int x_) :x{ x_ } { ; }
   virtual std::shared_ptr<A> fooInternal() {
     return std::make_shared<B>(x+1);
   }
};

Are there any caveats with these, and/or are there any other better alternatives?

Hans Olsson
  • 11,123
  • 15
  • 38
  • 2
    Already not making sense at the first sentence. Can you elaborate on what "keep track of the previous object in virtual function calls" means, exactly? – Ben Voigt Jul 08 '22 at 16:06
  • I have tried to do that. – Hans Olsson Jul 08 '22 at 16:09
  • If I could make any sense out of this question at all, I think it would be worth some kind of prize. – Paul Sanders Jul 08 '22 at 16:15
  • 5
    If the goal is for a newly-created object to be able to know "who created me", the answer is "use a pointer". Nothing smart, just an ordinary non-owning pointer. – Pete Becker Jul 08 '22 at 16:21
  • 1
    Oh, OK. Well worked out @PeteBecker. All you need to do then, OP, is to guarantee that the 'parent' outlives the 'child' (which it normally naturally would do anyway). – Paul Sanders Jul 08 '22 at 16:25
  • [Passkey idiom](https://stackoverflow.com/questions/63200550/passkey-idiom-with-stdmake-shared-xmemory-cannot-access-private-key-construct) might be useful. – Eljay Jul 08 '22 at 16:32
  • @PeteBecker The goal of shared_ptr is to safely simplify memory management, and a non-owning pointer isn't safe. The obvious idea of "keep all of them alive" would likely increase memory use. (There could be an exception, or a result ignored for other reasons etc.) – Hans Olsson Jul 11 '22 at 07:08
  • @HansOlsson -- a non-owning pointer, like any other construct, is safe when it is used correctly. In the question there is no indication of the relative lifetimes of the objects involved. Adding overhead "just in case" is, at best, lazy design. If you want to recommend some form of smart pointer get the information to justify the cost. – Pete Becker Jul 11 '22 at 12:31

0 Answers0