3

我在典型的菱形问题中使用虚拟继承:

            A
(virtual) /   \ (virtual)
        B       C
          \   /
            D

我在每个类中都实现了一个名为“deep_copy_from”的方法(但它也可以是赋值运算符=())。该方法应该复制类自己的属性,并将副本传播到上面的类。

问题是 A::deep_copy_from 方法在我深度复制 D 实例时被调用了两次(它应该只被调用一次,因为 A 只有一个“版本”)。确保它只被调用一次的最佳方法是什么?

(B::deep_copy_from 和 C::deep_copy_from 应该继续以相同的方式工作)。

这是一个示例代码:

class A
{
public:
    A(string const& p_a_name) : a_name(p_a_name) {
        cout << "A(a_name=\"" << p_a_name << "\")" << endl;
    }

    virtual void deep_copy_from(A const& a)
    {
        cout << "A::deep_copy_from(A(a_name=\"" << a.a_name << "\"))" << endl;
        this->a_name = a.a_name;
    }

protected:
    string a_name;
};

class B : public virtual A
{
public:
    B(string const &p_a_name, string const& p_b_name) : A(p_a_name), b_name(p_b_name) {
        cout << "B(a_name=\"" << p_a_name << "\", b_name=\"" << p_b_name << "\")" << endl;
    }

    virtual void deep_copy_from(B const& b)
    {
        cout << "B::deep_copy_from(B(a_name=\"" << b.a_name << "\", b_name=\"" << b.b_name << "\"))" << endl;
        this->A::deep_copy_from(static_cast<A const&>(b));
        this->b_name = b.b_name;
    }

protected:
    string b_name;
};

class C : public virtual A
{
public:
    C(string const &p_a_name, string const& p_c_name) : A(p_a_name), c_name(p_c_name) {
        cout << "C(a_name=\"" << p_a_name << "\", c_name=\"" << p_c_name << "\")" << endl;
    }

    virtual void deep_copy_from(C const& c)
    {
        cout << "C::deep_copy_from(C(a_name=\"" << c.a_name << "\", c_name=\"" << c.c_name << "\"))" << endl;
        this->A::deep_copy_from(static_cast<A const&>(c));
        this->c_name = c.c_name;
    }

protected:
    string c_name;
};

class D : public B, public C
{
public:
    D(string const &p_a_name, string const& p_b_name, string const& p_c_name, string const& p_d_name)
        : A(p_a_name), B(p_a_name, p_b_name), C(p_a_name, p_c_name), d_name(p_d_name)
    {
        cout << "D(a_name=\"" << p_a_name << "\", b_name=\"" << p_b_name
             << "\", c_name=\"" << p_c_name << "\", d_name=\"" << p_d_name << "\")" << endl;
    }

    virtual void deep_copy_from(D const& d)
    {
        cout << "D::deep_copy_from(D(a_name=\"" << d.a_name << "\", b_name=\"" << d.b_name
            << "\", c_name=\"" << d.c_name << "\", d_name=\"" << d.d_name << "\"))" << endl;
        this->B::deep_copy_from(static_cast<B const&>(d));
        this->C::deep_copy_from(static_cast<C const&>(d));
        this->d_name = d.d_name;
    }

protected:
    string d_name;
};

这是当前的输出:

A(a_name="A")
B(a_name="A", b_name="B")
C(a_name="A", c_name="C")
D(a_name="A", b_name="B", c_name="C", d_name="D")
D::deep_copy_from(D(a_name="A", b_name="B", c_name="C", d_name="D"))
B::deep_copy_from(B(a_name="A", b_name="B"))
A::deep_copy_from(A(a_name="A"))
C::deep_copy_from(C(a_name="A", c_name="C"))
A::deep_copy_from(A(a_name="A"))

更新:

现在的版本是:

class A
{
public:
    A(string const& p_a_name) : a_name(p_a_name) {
        cout << "A(a_name=\"" << p_a_name << "\")" << endl;
    }

    virtual void deep_copy_from(A const& a)
    {
        cout << "A::deep_copy_from(A(a_name=\"" << a.a_name << "\"))" << endl;
        this->a_name = a.a_name;
    }

protected:
    string a_name;
};

class B : public virtual A
{
public:
    B(string const &p_a_name, string const& p_b_name) : A(p_a_name), b_name(p_b_name) {
        cout << "B(a_name=\"" << p_a_name << "\", b_name=\"" << p_b_name << "\")" << endl;
    }

    virtual void deep_copy_from(B const& b)
    {
        cout << "B::deep_copy_from(B(a_name=\"" << b.a_name << "\", b_name=\"" << b.b_name << "\"))" << endl;
        this->A::deep_copy_from(static_cast<A const&>(b));
        this->deep_copy_my_bits_from(b);
    }

protected:
    void deep_copy_my_bits_from(B const& b) {
        cout << "B::deep_copy_my_bits_from(B(a_name=\"" << b.a_name << "\", b_name=\"" << b.b_name << "\"))" << endl;
        this->b_name = b.b_name;
    }

protected:
    string b_name;
};

class C : public virtual A
{
public:
    C(string const &p_a_name, string const& p_c_name) : A(p_a_name), c_name(p_c_name) {
        cout << "C(a_name=\"" << p_a_name << "\", c_name=\"" << p_c_name << "\")" << endl;
    }

    virtual void deep_copy_from(C const& c)
    {
        cout << "C::deep_copy_from(C(a_name=\"" << c.a_name << "\", c_name=\"" << c.c_name << "\"))" << endl;
        this->A::deep_copy_from(static_cast<A const&>(c));
        this->deep_copy_my_bits_from(c);
    }

protected:
    void deep_copy_my_bits_from(C const& c) {
        cout << "C::deep_copy_my_bits_from(C(a_name=\"" << c.a_name << "\", c_name=\"" << c.c_name << "\"))" << endl;
        this->c_name = c.c_name;
    }

protected:
    string c_name;
};

class D : public B, public C
{
public:
    D(string const &p_a_name, string const& p_b_name, string const& p_c_name, string const& p_d_name)
        : A(p_a_name), B(p_a_name, p_b_name), C(p_a_name, p_c_name), d_name(p_d_name)
    {
        cout << "D(a_name=\"" << p_a_name << "\", b_name=\"" << p_b_name
             << "\", c_name=\"" << p_c_name << "\", d_name=\"" << p_d_name << "\")" << endl;
    }

    virtual void deep_copy_from(D const& d)
    {
        cout << "D::deep_copy_from(D(a_name=\"" << d.a_name << "\", b_name=\"" << d.b_name
            << "\", c_name=\"" << d.c_name << "\", d_name=\"" << d.d_name << "\"))" << endl;
        this->A::deep_copy_from(static_cast<A const&>(d));
        this->B::deep_copy_my_bits_from(static_cast<B const&>(d));
        this->C::deep_copy_my_bits_from(static_cast<C const&>(d));
        this->d_name = d.d_name;
    }

protected:
    string d_name;
};

输出是:

A(a_name="A")
B(a_name="A", b_name="B")
C(a_name="A", c_name="C")
D(a_name="A", b_name="B", c_name="C", d_name="D")
D::deep_copy_from(D(a_name="A", b_name="B", c_name="C", d_name="D"))
A::deep_copy_from(A(a_name="A"))
B::deep_copy_my_bits_from(B(a_name="A", b_name="B"))
C::deep_copy_my_bits_from(C(a_name="A", c_name="C"))

我能得到比这更好的吗?(即,更自动)

4

3 回答 3

2

@Alf 关于分配是正确的:分配被搞砸了。原因是它是二元运算,由于协方差问题,不可能在 OO 框架中调度二元运算。

现在,您的问题有了一个一般性的答案,但首先您需要知道两件事。首先是虚拟基地总是公开的,无论你声明什么,无论标准怎么说:标准是错误的。[证明:只需派生另一个类并再次声明任何虚拟基公共虚拟,您就可以访问]

第二个事实是,虚拟基是每个类的直接基,它们是它们的间接基。再次,忽略标准,因为它是错误的。往上看。

鉴于这两个事实,很容易看出避免重复的正确模式:

这是你的钻石:

struct A { cp(){ "A" } virtual CP(){ cp(); } };
struct B : virtual A { cp(){ "B" } CP() { cp(); A::CP(); } };
struct C : ... ibid ...
struct D : B, C, virtual A { 
   cp() { "D"; B::cp(); C::cp(); }
   CP() { cp(); A::cp(); }
};

为简洁起见,我省略了返回类型和其他内容。cp() 函数通过首先处理任何成员然后调用每个非虚拟基来处理其成员(递归)来向下钻取。实际上它应该受到保护,因为它不适合公共客户。向下钻取是强制性的,因为您自己不能访问间接的非虚拟基础,只能访问直接的。

CP() 函数是虚拟的,因此无论您使用哪个指针(A、B、C 或 D)访问菱形,任何调用都会转到唯一 CP 的完整对象。

它通过调用自己类的 cp() 处理所有成员和非虚拟基子对象成员,然后处理虚拟基,在这种情况下只有一个,即 A。

如果 X::CP() 变成

X *X::clone() const;

then if you can clone the complete object off any pointer and get back the same dynamic and static types: if your dynamic type is D and static type is B, you will get a B* to a D object exactly as you started with.

It is NOT possible to do assignment this way. It is NOT possible to make assignment work at all. The reason is that assignment is covariant on two arguments. There's no way to ensure the source and target have the same dynamic type, and that is necessary for assignment to work. If the source is too big, some of it gets sliced off. Far worse, if the target is too big, some of it never gets assigned to. Therefore it makes no difference which object (the target or source) you dispatch on: it just can't work. The only kind of assignment that can work is a non-virtual one based on static type. That can over or under slice as well, but at least the issue is statically evident.

Cloning works because it is a function with only one argument (namely, the self-object). Generally, if you're using "object stuff" as opposed to "value stuff" then, since you can only really manipulate values, you have to use pointers. In that case clone() and friends are just what you want: you can assign a pointer just fine!

于 2010-12-06T18:42:49.913 回答
1

有两个问题:一个关于双重复制到A部分,一个关于虚拟分配操作。

虚拟分配:不是一个好主意,因为它将错误检测从编译时转移到运行时。干脆不要。似乎需要虚拟分配(或像您的类似分配的操作)的一般解决方案是实现克隆,这是一种clone产生动态分配副本的虚拟成员函数。

双重复制:简单的答案是用构造来表达赋值。执行此操作的惯用方式称为“交换习惯用法”。简单地说,构造一个副本,然后将其内容与当前实例交换,然后让您构造的实例的析构函数负责清理。

干杯&hth.,

于 2010-12-06T16:37:15.713 回答
0

deep_copy_from 不是协变的,您只能在返回类型中使用协变。

您编写的代码可能会收到“重载隐藏虚拟功能”警告。

由于不调用 A 版本就无法调用 B 或 C 版本,因此除非您修改 B 或 C,否则如果您必须同时调用 B 和 C 版本,则无法避免 A 版本被调用两次。

鉴于 B 和 C 都从 A 继承 virtual,您可能不应该让它们调用 A 版本,因为最终类负责 A 部分。

于 2010-12-06T13:45:17.663 回答