4

I have a simple class of Node tree MultNode. It's subclass of Node. _pRight and _pLeft are std::shared_ptr<Node> objects. Class Node is an abstract class. MultNode::DerFun() performs derivative function for multiplication Node.

\frac{d}{dx}[f(x)g(x)]=f(x)g'(x)+f'(x)g(x)

I have to create temporary right and left Node(pTmpR and pTmpL) and I need use only smart pointers. I have no idea how to create deep copy of shrared_ptr.

void MultNode::DerFun() {
    auto pTmpR =  _pRight;
    auto pTmpL = _pLeft;
    _pRight->DerFun(); // sin(pi/4) * cos(pi/4);
    _pLeft->DerFun();  // der(sin(pi/4) * cos(pi/4));
    _pRight = std::make_shared<AddNode>(
        std::make_shared<MultNode>(_pRight, pTmpL),
        std::make_shared<MultNode>(pTmpR, _pLeft));
    _pLeft = std::make_shared<NumNode>(1);
}
Benjamin Buch
  • 4,752
  • 7
  • 28
  • 51
user11225404
  • 107
  • 1
  • 6
  • `*ptr` is the object managed by the pointer – 463035818_is_not_an_ai Mar 15 '23 at 08:55
  • When its a temporary there is no point in making it shared – 463035818_is_not_an_ai Mar 15 '23 at 08:56
  • 5
    If I understand your question correctly, you seem to be building an expression tree, where `MultNode` represents a multiplication. `DerFun` seems to be a virtual `void` method of `Node`, that replaces a node in the expression tree with its derivative, mutating the original node. Conceptually, the derivative of a `MultNode` is an `AddNode`, but you just replace one operand of the `MultNode` with an `AddNode` and set the other operand to 1. While this may work, I would argue that it's a major design flaw. Wy not make `DerFun` a `const` method and have it return a new node of the correct type? – joergbrech Mar 15 '23 at 09:19
  • 1
    I think you're thinking too much - if you want a copy of the object owned by `p`, and you want the copy to be shared: `std::make_shared(*p);` – molbdnilo Mar 15 '23 at 09:32
  • When you have a class hierarchy and you want to make a deep copy, one technique is to add a virtual `clone` method to each class, that method allocates a deep copy of itself. Wrap those allocations in `std::shared_ptr<>` and I think you have what you are looking for. – john Mar 15 '23 at 09:40
  • 4
    If you go down this path, you're going to find yourself writing a lot of horrible and messy code to clean up `* 1`s and `+ 0`s. It's better to have differentiation create a new tree instead of updating it destructively. – molbdnilo Mar 15 '23 at 10:24

2 Answers2

4

deep copying a Derived by a Base pointer cannot be done by the compiler because it won't know what to copy at compile-time.

the solution is to make a virtual deepcopy function, which is responsible for making a deepcopy of this derived object, which will call deepcopy on all of its children to construct a true deepcopy, and since it is called on a pointer the correct function will be called at runtime.

#include <memory>

class Node {
public:
    virtual std::shared_ptr<Node> deepcopy() const = 0;
    virtual ~Node() = default;
};

class MultNode: public Node {
private:
    std::shared_ptr<Node> m_pleft;
    std::shared_ptr<Node> m_pright;
public:
    virtual std::shared_ptr<Node> deepcopy() const
    {
        return std::make_shared<MultNode>(m_pleft->deepcopy(), m_pright->deepcopy());
    }
    MultNode(std::shared_ptr<Node> pleft, std::shared_ptr<Node> pright) : 
        m_pleft(pleft), m_pright(pright) {};
};
Ahmed AEK
  • 8,584
  • 2
  • 7
  • 23
3

There is no need to deep copy anything at all. You have shared pointers, use them to your advantage.

Here is a minimal system that can derive. Note it builds a DAG, not a tree. Everything is const, nothing is ever changed. The nodes are shared as much as possible.

#include <memory>

class Node;

using NodePtr = std::shared_ptr<const Node>;

using Number = double;

class Node
{
    public:
        virtual ~Node() = default;
        virtual NodePtr derive() const = 0;
};

class AddNode : public Node
{
    public:
        AddNode (const NodePtr& a, const NodePtr& b) : a(a), b(b) {}
    private:
        NodePtr a, b;
        NodePtr derive() const override 
        { 
            return std::make_shared<AddNode>(a->derive(), b->derive()); 
        }
};

class MultNode : public Node
{
    public:
        MultNode (const NodePtr& a, const NodePtr& b) : a(a), b(b) {}
    private:
        NodePtr a, b;
        NodePtr derive() const override
        {
            const auto l = std::make_shared<MultNode>(a, b->derive());
            const auto r = std::make_shared<MultNode>(a->derive(), b);
            return std::make_shared<AddNode>(l, r);
        }
};

class ConstNode : public Node
{
    public:
        ConstNode (Number v) : v(v) {}
    private:
        Number v;
        NodePtr derive() const override 
        { 
            return std::make_shared<ConstNode>(Number{0}); 
        }
};

class VarNode : public Node
{
    public:
        VarNode() {}
    private:
         NodePtr derive() const override 
         { 
             return std::make_shared<ConstNode>(Number{1}); 
         }
};

Caveats:

  1. Not tested much
  2. Only one variable supported (easy to change)
  3. Many useful nodes missing, e.g. division, function composition, power, ... (easy to add)
  4. No constant propagation or other means to simplify expressions (can be added as a separate method)
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • 1
    You could even define `NodePtr operator*(NodePtr l, NodePtr r) { return std::make_shared(l, r); }` etc – Caleth Mar 15 '23 at 11:41
  • Yes. This is the stuff. Sean Parent® approved – sehe Mar 15 '23 at 14:53
  • I just remembered I made a similar comment in the past and felt bad not to give an example, so here goes: https://stackoverflow.com/questions/27708139/transforming-trees-in-c/27715495#27715495 – sehe Mar 15 '23 at 14:55