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

#c

#c #объект-нарезка

Вопрос:

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

 #include "stdafx.h"
#include <memory>
#include <vector>
#include <string>
#include <iostream>

class Foo
{
public:
    Foo() { m_x = "abc"; }
    Foo( const Foo amp;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 amp;other )
    {
        for ( auto amp;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 amp;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. Как я могу скопировать данные без нарезки?

Комментарии:

1. Несколько вещей: 1) Почему вы не вызываете конструктор суперкласса в производных классах? 2) auto может быть только для примитивных типов (я могу ошибаться). 3) Входит ли использование виртуальных классов в вашу область использования?

2. Поиск «clone» ( пример , example ) или «virtual copy constructor» ( пример , example )

3. @hagubear Извините, я просто не думал, что это имеет отношение к примеру, хотя вы правы, что я должен был. Я отредактирую код. @ gx_ Спасибо. Я рассмотрю это.

4. @PhloxMidas Полезный вопрос, хотя! вопросы нарезки, по-видимому, повторяются в stackoverflow, и gx_, похоже, указал нам правильное направление. Удачи!

Ответ №1:

Классическим методом решения этой проблемы является реализация a 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 или любом из его производных типов, поскольку виртуальные функции не работают «правильно» внутри конструкторов.

Комментарии:

1. Это кажется обратным: clone вызывает конструктор копирования и не вызывается из него (и нет такого понятия, как «конструктор присваивания»).

2. … может быть, виртуальный Foo* clone() const ? Вы также имеете в виду конструктор копирования класса Foos (это не слишком ясно из вашего ответа)?

Ответ №2:

Вы можете рассмотреть возможность использования 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 constamp; foo) const 
        {return foo.m_x;}
    std::string operator()(FooDerivedA constamp; foo) const
        {return foo.m_x   ", "   std::to_string(foo.m_y);}
    std::string operator()(FooDerivedB constamp; foo) const
        {return foo.m_x   ", "   std::to_string(foo.m_z);}
};

std::ostreamamp; operator<<(std::ostreamamp; os, a_foo constamp; 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;
}