Reputation: 685
In the below example there is a base class and two derived classes. The derived classes are no different from each other but I included it simply to illustrate that there can be multiple child classes. The code creates a derived class (implicitly up-cast, think the factory design pattern) which is cloned polymorphically to a smart pointer (exactly as was done here Clone pattern for std::shared_ptr in C++) and then an attribute of the class is changed to show the cloning has worked as intended. This runs great and returns the desired output "Source:4 Clone:1". I wanted to know if its possible to remover the boiler plate code though?
#include "pch.h"
#include <iostream>
class Node
{
public:
Node(int);
int value;
virtual ~Node() = default;
virtual void set_value(int) = 0;
std::shared_ptr<Node> Clone() const;
private:
virtual Node * CloneImplementation() const = 0;
};
Node::Node(int VALUE) {
value = VALUE;
}
std::shared_ptr<Node> Node::Clone() const {
return std::shared_ptr<Node>(CloneImplementation());
};
class DerivedNode1 : public Node
{
public:
DerivedNode1(int);
void set_value(int);
std::shared_ptr<DerivedNode1> Clone() const;
private:
DerivedNode1 * CloneImplementation() const override;
};
DerivedNode1::DerivedNode1(int VALUE) : Node(VALUE) {
}
std::shared_ptr<DerivedNode1> DerivedNode1::Clone() const {
return std::shared_ptr<DerivedNode1>(CloneImplementation());
};
DerivedNode1 * DerivedNode1::CloneImplementation() const {
return new DerivedNode1(*this);
};
void DerivedNode1::set_value(int NEW_VALUE) {
value = NEW_VALUE;
}
class DerivedNode2 : public Node
{
public:
DerivedNode2(int);
void set_value(int);
std::shared_ptr<DerivedNode2> Clone() const;
private:
DerivedNode2 * CloneImplementation() const override;
};
DerivedNode2::DerivedNode2(int VALUE) : Node(VALUE) {
}
std::shared_ptr<DerivedNode2> DerivedNode2::Clone() const {
return std::shared_ptr<DerivedNode2>(CloneImplementation());
};
DerivedNode2 * DerivedNode2::CloneImplementation() const {
return new DerivedNode2(*this);
};
void DerivedNode2::set_value(int NEW_VALUE) {
value = NEW_VALUE;
}
std::shared_ptr<Node> ImplicitUpCast(int type) {
if (type == 1) {
return std::make_shared<DerivedNode1>(1);
} else {
return std::make_shared<DerivedNode2>(1);
}
}
int main() {
std::shared_ptr<Node> sourceNode1 = ImplicitUpCast(1);
std::shared_ptr<Node> cloneNode1 = sourceNode1->Clone();
sourceNode1->set_value(4);
std::cout << "Source: " << sourceNode1->value << " Clone: " << cloneNode1->value;
}
Making and declaring the below methods in each child class seems tedious and I wanted a workaround.
std::shared_ptr<DerivedNode1> DerivedNode1::Clone() const {
return std::shared_ptr<DerivedNode1>(CloneImplementation());
};
DerivedNode1 * DerivedNode1::CloneImplementation() const {
return new DerivedNode1(*this);
};
My approach was to use an 'intermediary' class template that DerivedNode1
and DerivedNode2
inherited from and contained both these methods. Here is my attempt.
#include "pch.h"
#include <iostream>
class Node
{
public:
Node(int);
int value;
virtual ~Node() = default;
virtual void set_value(int) = 0;
std::shared_ptr<Node> Clone() const;
private:
virtual Node * CloneImplementation() const = 0;
};
Node::Node(int VALUE) {
value = VALUE;
}
std::shared_ptr<Node> Node::Clone() const {
return std::shared_ptr<Node>(CloneImplementation());
};
template<class source>
class NodeExtended : public Node
{
public:
NodeExtended(int);
std::shared_ptr<source> Clone() const;
private:
source* CloneImplementation() const override;
};
template<class source>
NodeExtended<source>::NodeExtended(int VALUE) : Node(VALUE) {
}
template<class source>
std::shared_ptr<source> NodeExtended<source>::Clone() const {
return std::shared_ptr<source>(CloneImplementation());
};
template<class source>
source * NodeExtended<source>::CloneImplementation() const {
return new source(*this);
};
class DerivedNode1 : public NodeExtended<DerivedNode1>
{
public:
DerivedNode1(int);
void set_value(int);
};
DerivedNode1::DerivedNode1(int VALUE) : NodeExtended(VALUE) {
}
void DerivedNode1::set_value(int NEW_VALUE) {
value = NEW_VALUE;
}
class DerivedNode2 : public NodeExtended<DerivedNode2>
{
public:
DerivedNode2(int);
void set_value(int);
};
DerivedNode2::DerivedNode2(int VALUE) : NodeExtended(VALUE) {
}
void DerivedNode2::set_value(int NEW_VALUE) {
value = NEW_VALUE;
}
std::shared_ptr<Node> ImplicitUpCast(int type) {
if (type == 1) {
return std::make_shared<DerivedNode1>(1);
} else {
return std::make_shared<DerivedNode2>(1);
}
}
int main() {
//std::shared_ptr<Node> sourceNode1 = ImplicitUpCast(1);
//std::shared_ptr<Node> cloneNode1 = sourceNode1->Clone();
//sourceNode1->set_value(4);
//std::cout << "Source: " << sourceNode1->value << " Clone: " << cloneNode1->value;
}
I receive the error 'NodeExtended::CloneImplementation': unable to match function definition to an existing declaration' on the following line
source * NodeExtended<source>::CloneImplementation() const {
return new source(*this);
};
I am assuming this is because Node and source are not viewed as parent and child pointers and the polymorphism breaks down because of the return type, similarly to how a smart pointer might throw an error in the situation.
I am unsure how to continue from here. I think I don't understand class templates well enough but no tutorials really capture my use case.
Any and all help would be appreciated.
Edit:
The approved answer finds the main problem but I also had to change some of the source return types to node which still gives the desired behavior. Here is the complete solution.
#include "pch.h"
#include <iostream>
class Node
{
public:
Node(int);
int value;
virtual ~Node() = default;
virtual void set_value(int) = 0;
std::shared_ptr<Node> Clone() const;
private:
virtual Node * CloneImplementation() const = 0;
};
Node::Node(int VALUE) {
value = VALUE;
}
std::shared_ptr<Node> Node::Clone() const {
return std::shared_ptr<Node>(CloneImplementation());
};
template<class source>
class NodeExtended : public Node
{
public:
NodeExtended(int);
std::shared_ptr<source> Clone() const;
private:
Node * CloneImplementation() const override;
};
template<class source>
NodeExtended<source>::NodeExtended(int VALUE) : Node(VALUE) {
}
template<class source>
std::shared_ptr<source> NodeExtended<source>::Clone() const {
return std::shared_ptr<source>(static_cast<source*>(CloneImplementation()));
};
template<class source>
Node * NodeExtended<source>::CloneImplementation() const {
return new source(static_cast<const source&>(*this));
};
class DerivedNode1 : public NodeExtended<DerivedNode1>
{
public:
DerivedNode1(int);
void set_value(int);
};
DerivedNode1::DerivedNode1(int VALUE) : NodeExtended(VALUE) {
}
void DerivedNode1::set_value(int NEW_VALUE) {
value = NEW_VALUE;
}
class DerivedNode2 : public NodeExtended<DerivedNode1>
{
public:
DerivedNode2(int);
void set_value(int);
};
DerivedNode2::DerivedNode2(int VALUE) : NodeExtended(VALUE) {
}
void DerivedNode2::set_value(int NEW_VALUE) {
value = NEW_VALUE;
}
std::shared_ptr<Node> ImplicitUpCast(int type) {
if (type == 1) {
return std::make_shared<DerivedNode1>(1);
} else {
return std::make_shared<DerivedNode2>(1);
}
}
int main() {
std::shared_ptr<Node> sourceNode1 = ImplicitUpCast(1);
std::shared_ptr<Node> cloneNode1 = sourceNode1->Clone();
sourceNode1->set_value(4);
std::cout << "Source: " << sourceNode1->value << " Clone: " << cloneNode1->value;
}
Upvotes: 0
Views: 326
Reputation: 217275
Issue with CRTP is that class is not yet a complete type. which makes Clonable more difficult and requires some casts.
template<class source>
class NodeExtended : public Node
{
public:
using Node::Node; // inherit constructors
std::shared_ptr<source> Clone() const
{
return std::shared_ptr<source>(static_cast<source*>(CloneImplementation()));
}
private:
// Cannot use covariant return type as source is not complete.
Node* CloneImplementation() const override
{
return new source(static_cast<const source&>(*this));
}
};
Upvotes: 1
Reputation: 46
std::shared_ptr<DerivedNode1> DerivedNode1::Clone() const
and std::shared_ptr<DerivedNode2> DerivedNode2::Clone() const
not called in your sample. All works fine without these functions. In template example in function
template<class source>
source * NodeExtended<source>::CloneImplementation() const {
return new source(*this);
};
you try to call source(NodeExtended &)
constructor. You need something like
return new source(*static_cast<const source*>(this));
Then all should work.
Upvotes: 0