防止在复制构造函数中切片

时间:2013-06-25 08:39:31

标签: c++ object-slicing

我想复制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。如何在不切片的情况下复制数据?

2 个答案:

答案 0 :(得分:7)

解决此问题的经典方法是实现virtual Foo *clone() const,然后调用它而不是复制构造函数。

所以,如果我们在Foo中有一些({1}}的{​​{1}}对象,我们可以通过以下方式创建另一个:

x

请注意,由于它是一个虚函数,我们无法在 void someFunc(Foo *x) { Foo *copy_of_x = x->clone(); ... delete copy_of_x; // Important so we don't leak! } 或其任何派生类型的构造函数中调用它,因为虚函数不能在构造函数内“正确”运行。

答案 1 :(得分: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 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;
}