开发者

How to ensure that the assignment operator on a virtual base class is called only once?

I'm using virtual inheritance as in the typical diamond problem:

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

I'm implementing a method named "deep_copy_from" in every class (but it could be the assignment operator=() as well). The method should copy the class own attributes, and propagate the copy to the classes above.

The problem is that the A::deep_copy_from method is called twice when I'm deep copying a D instance (and it should be called only once, as there is only one "version" of A). What is the best way to ensure it will be called only once?

(B::deep_copy_from and C::deep_copy_from should continue working the same way).

Here is a sample code:

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

Here is the current output:

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"))

Update:

The current version is now:

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 << "\"))" &l开发者_如何学编程t;< 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;
};

And the output is:

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"))

Can I get anything better than that? (i.e., more automatic)


@Alf is right about assignment: assignment is screwed. The reason is that it is a binary operation and it is impossible to dispatch binary operations in an OO framework due to the covariance problem.

Now, there is a general answer to your question, but first there are two things you need to know. The first is that virtual bases are always public, no matter what you declare and no matter what the Standard says: the Standard is just wrong. [Proof: just derive another class and declare any virtual base public virtual again, and you have access]

The second fact is that virtual bases are direct bases of every class they're indirect bases of. Again, ignore the Standard, because it is wrong. See above.

Given these two facts, it is easy to see the proper pattern to avoid duplication:

Here's your diamond:

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(); }
};

I left off return types and other stuff for brevity. The cp() function drills down by first processing any members then calling on each non-virtual base to process its members (recursively). Actually it should be protected, since it isn't for public clients. The drilling down is mandatory because you can't access indirect non-virtual bases yourself, only direct ones.

The CP() function is virtual, so any call goes to the complete objects unique CP no matter which pointer (A, B, C, or D) you're accessing the diamond with.

It processes all the members and non-virtual base subobject members by calling cp() of its own class, then it processes the virtual bases, in this case there is only one, namely A.

If X::CP() becomes

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!


There are two problems: the one about double copying to A part, and the one about virtual assignment op.

Virtual assignment: not a good idea, because it transfers error detection from compile time to run time. Simply don't. The general solution where virtual assignment (or assignment-like op like yours) seems to be required, is to implement cloning instead, a virtual clone member function that produces a dynamically allocated copy.

Double copying: the simple answer is to express assignment in terms of construction. The idiomatic way to do this is known as the "swap idiom". Simply put, construct a copy, then swap its contents with current instance, then let destructor of the instance you constructed take care of cleanup.

Cheers & hth.,


deep_copy_from is not co-variant, you can only use covariance in return types.

You may get "overload hides virtual function" warnings with the code you have written.

As it is there is no way to call the B or C version without invoking the A version so unless you modify B or C you cannot avoid the A version getting called twice if you have to call both the B and C version.

Given that B and C both inherit virtual from A you should probably not get them to invoke the A version as the final class is responsible for the A part.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜