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:
- Not tested much
- Only one variable supported (easy to change)
- Many useful nodes missing, e.g. division, function composition, power, ... (easy to add)
- No constant propagation or other means to simplify expressions (can be added as a separate method)