C++中使用派生类重载赋值运算符=

3

我目前正在编写一个复杂的类,在其中我基本上需要复制一组派生类。简化版如下所示: 我有一个基类,从中我派生出几个其他类:

class Base
{
public:
    virtual void test(void)
    {
        cout << "Base" << endl;
    }
    Base(vector<Base*> *pointer)
    {
        pointer->push_back(this);
    }
    virtual Base& operator=(const Base& rhs)
    {
        cout << "Base=" << endl;
        return *this;
    }
};
class A : public Base
{
public:
    void test(void)
    {
        cout << "A" << endl;
    }
    A(vector<Base*> *pointer) : Base(pointer) {}
    A& operator=(const A& rhs)
    {
        cout << "A=" << endl;
        return *this;
    }
};
class B : public Base
{
public:
    void test(void)
    {
        cout << "B" << endl;
    }
    B(vector<Base*> *pointer) : Base(pointer) {}
    B& operator=(const B& rhs)
    {
        cout << "B=" << endl;
        return *this;
    }
};

然后我创建了一个对象列表,并将其保存在基类的指针列表中:
vector<Base*> listA;

new Base(&listA);
new A(&listA);
new B(&listA);

我希望将这些对象复制到一个与原列表相同顺序和类的第二个列表中,但该列表可能具有不同的值。

for (int i = 0; i < (int)listA.size(); i++)
{
    (*listA[i]) = (*listB[i]);
}

然而,C++无法做到这一点。因为列表具有Base*类型,解引用会创建一个Base类型的对象。因此,赋值运算符=调用的是Base类的赋值运算符,而不是派生类中正确的赋值运算符。我该如何解决这个问题?

或者我该如何告诉C++使用正确的运算符?也许可以通过某种isinstanceof函数实现?

完整示例请参见:

int main()
{
    vector<Base*> listA;

    new Base(&listA);
    new A(&listA);
    new B(&listA);

    vector<Base*> listB;

    new Base(&listB);
    new A(&listB);
    new B(&listB);


    for (int i = 0; i < (int)listA.size(); i++)
    {
        (*listA[i]).test();
    }
    for (int i = 0; i < (int)listA.size(); i++)
    {
        (*listA[i]) = (*listB[i]);
    }
}

这将输出:

Base
A
B
Base=
Base=
Base=

A赋值给B或者反过来是什么意思? - undefined
2个回答

5
这里有一些误解。首先,将派生类的实例分配给基类的实例是什么意思?让我们来看一个简单的层次结构:
struct A { int x; };
struct B : A { int y; };

A a;
B b;
a = b; // what should this do?
b = a; // what about this?

使用普通的C++,第一个会进行对象切片,而第二个则是不良形式。但即使第一个被良好形式化,它通常也不是你想要做的。你确定你想要进行切片吗?
第二点是,虽然你将赋值运算符声明为虚函数:
virtual Base& operator=(const Base& rhs)

没有任何一个派生类真正重载它。 A 的赋值运算符接受一个 A const&,而 B 的赋值运算符则接受一个 B const&。如果你用 override 标记这两个函数,编译器会指出错误。如果你把这两个函数改成接受一个 Base const& 参数,那么你就可以得到你想要的输出结果了——但这可能仍然不是你真正想要发生的事情。


为了实际进行多态复制,一种典型的解决方案是提供虚拟克隆方法:
virtual Base* clone() const = 0;

你的派生类实现:

struct A : Base {
    A* clone() const override { return new A(*this); }
};

然后使用clone()而不是赋值,这里不会有切片。


在这里插入有关内存管理和原始指针的常规警告。


谢谢!我明白你在说什么。只有最后一部分让我有些困惑。我该如何在实践中使用这个克隆函数呢? - undefined
你的意思是完全替换旧对象吗?很遗憾,这不是一个选择! - undefined

0
好的。我找到了解决我的问题的方法。我实现了一个复制函数,它以基类作为参数。在这个复制函数内部,我可以使用pointa来复制变量。现在类的结构如下:
class Base
{
public:
    virtual void test(void)
    {
        cout << "Base" << endl;
    }
    Base(vector<Base*> *pointer)
    {
        pointer->push_back(this);
    }
    virtual void clone(Base* pointer) = 0;
};
class A : public Base
{
public:
    void test(void)
    {
        cout << "A" << endl;
    }
    A(vector<Base*> *pointer) : Base(pointer) {}
    void clone(Base* pointer) override
    {
        A* pointa = (A*)pointer;
        cout << "clone A" << endl;
        //Clone Variables here
    }
};
class B : public Base
{
public:
    void test(void)
    {
        cout << "B" << endl;
    }
    B(vector<Base*> *pointer) : Base(pointer) {}
    void clone(Base* pointer) override
    {
        B* pointa = (B*)pointer;
        cout << "clone B" << endl;
        //Clone Variables here
    }
};

这意味着我现在可以以以下方式复制对象:
for (int i = 0; i < (int)listA.size(); i++)
{
    listA[i]->clone(listB[i]);
}

不过这个解决方案在任何类型安全的方式下都不适用,而这是我想要满足的要求。我研究了我的想法,并决定手动处理,不使用列表,这意味着会有很多重复的代码,但却能带来内心的安宁。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接