Предотвращение нарезки в конструкторе копирования

Я хочу скопировать вектор объектов типа Foo, но объекты могут быть нескольких различных производных типов Foo. Я не могу понять, как копировать без нарезки. Вот мой игрушечный код

#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;
}

Я не могу указать, что тип должен быть FooDerivedA, как:

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

потому что это может быть FooDerivedB. Как я могу скопировать данные без нарезки?

3

Решение

Классический метод решения этой проблемы заключается в реализации virtual Foo *clone() const, который затем вызывается вместо конструктора копирования.

Итак, если у нас есть объект какой-то (производная форма) Foo в xмы можем создать еще один:

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

Обратите внимание, что, поскольку это виртуальная функция, мы не можем вызвать ее в конструкторе foo или любой из его производных типов, поскольку виртуальные функции не работают «правильно» внутри конструкторов.

7

Другие решения

Вы можете рассмотреть возможность использования Boost.Variant вместо указателей в вашем контейнере.
Это позволяет избежать многих проблем с нарезкой и управлением памятью. Кроме того, вы получаете намного больше от конструкторов по умолчанию.

Вот полная переделка вашего примера с использованием этого дизайна:

#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

По вопросам рекламы [email protected]