Prevent slicing in copy constructor

I want to copy a vector of objects of type Foo, but objects can be several different derived types of Foo. I can’t figure out how to copy without slicing. Here is my toy code

#include "stdafx.h" #include <memory> #include <vector> #include <string> #include <iostream> class Foo { public: Foo() { m_x = "abc"; } Foo( const Foo &other ) { m_x = other.m_x; } virtual std::string ToString() { return m_x; } std::string m_x; }; class FooDerivedA : public Foo { public: FooDerivedA() : Foo() { m_y = 123; } std::string ToString() { return m_x + ", " + std::to_string( m_y ); } int m_y; }; class FooDerivedB : public Foo { public: FooDerivedB() : Foo() { m_z = true; } std::string ToString() { return m_x + ", " + std::to_string( m_z ); } bool m_z; }; class Foos { public: Foos(){} Foos( const Foos &other ) { for ( auto &foo : other.m_Foos ) { // I believe this is slicing. How can I prevent this? auto f = std::unique_ptr<Foo>( new Foo( *foo ) ); m_Foos.push_back( std::move( f ) ); } } void Add( std::unique_ptr<Foo> foo ) { m_Foos.push_back( std::move( foo ) ); } std::string ToString() { std::string s; for ( auto &foo : m_Foos ) { s += foo->ToString() + "\n"; } return s; } private: std::vector<std::unique_ptr<Foo>> m_Foos; }; int main() { Foos f1; f1.Add( std::unique_ptr<FooDerivedA>( new FooDerivedA ) ); auto f2 = Foos( f1 ); std::cout << "f1:" << f1.ToString() << std::endl; std::cout << "f2:" << f2.ToString() << std::endl; system("pause"); return 0; } 

I cannot indicate that the type must be FooDerivedA, for example:

 auto f = std::unique_ptr<Foo>( new FooDerivedA( *foo ) ); 

because it could be FooDerivedB. How to copy data without slicing?

+2
source share
2 answers

The classic way to solve this problem is to implement virtual Foo *clone() const , which is then called instead of the copy constructor.

So, if we have an object of some (derived form) Foo in x , we can create another:

  void someFunc(Foo *x) { Foo *copy_of_x = x->clone(); ... delete copy_of_x; // Important so we don't leak! } 

Note that since this is a virtual function, we cannot call it in the constructor of Foo or any of its derived types, since virtual functions do not work β€œcorrectly” inside the constructors.

+7
source

You can use Boost.Variant instead of pointers in your container. This avoids many environmental and memory management issues. In addition, you get a lot more from the default constructors.

Here is a complete redistribution of your example using this design:

 #include <vector> #include <iterator> #include <string> #include <boost/variant.hpp> struct Foo { Foo() : m_x("abc") {} std::string m_x; }; struct FooDerivedA : Foo { FooDerivedA() : m_y(123) {} int m_y; }; struct FooDerivedB : Foo { FooDerivedB() : m_z(true) {} bool m_z; }; typedef boost::variant<FooDerivedA, FooDerivedB> a_foo; struct to_string : boost::static_visitor<std::string> { std::string operator()(Foo const& foo) const {return foo.m_x;} std::string operator()(FooDerivedA const& foo) const {return foo.m_x + ", " + std::to_string(foo.m_y);} std::string operator()(FooDerivedB const& foo) const {return foo.m_x + ", " + std::to_string(foo.m_z);} }; std::ostream& operator<<(std::ostream& os, a_foo const& foo) { return os << boost::apply_visitor(to_string(), foo); } int main() { std::vector<a_foo> f1; f1.push_back(FooDerivedA()); f1.push_back(FooDerivedB()); auto f2 = f1; std::ostream_iterator<a_foo> out_it(std::cout, "\n"); std::cout << "f1:" << std::endl; std::copy(f1.begin(), f1.end(), out_it); std::cout << "f2:" << std::endl; std::copy(f2.begin(), f2.end(), out_it); return 0; } 
+2
source

All Articles