Как обеспечить, чтобы оператор присваивания в виртуальном базовом классе вызывался только один раз? - PullRequest
3 голосов
/ 06 декабря 2010

Я использую виртуальное наследование, как в типичной проблеме с бриллиантами:

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

Могу ли я получить что-нибудь лучше, чем это? (то есть, более автоматический)

Ответы [ 3 ]

2 голосов
/ 06 декабря 2010

@ Альф прав насчет назначения: назначение ввернуто.Причина в том, что это бинарная операция, и невозможно выполнить бинарные операции в 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 () является виртуальной, поэтому любой вызов передается целым объектам, уникальным CP независимо от того, какой указатель(A, B, C или D) вы обращаетесь к алмазу с помощью.

Он обрабатывает все члены и не виртуальные базовые члены подобъекта, вызывая cp () своего собственного класса, затем обрабатываетвиртуальные базы, в этом случае есть только одна, а именно: A.

Если X :: CP () становится

X *X::clone() const;

, тогда, если вы можете клонировать весь объект из любого указателя и получитьобратно те же динамические и статические типы: если ваш динамический тип - D, а статический тип - B, вы получите B * для объекта D именно так, как вы начали.

Невозможно сделать это назначениепуть.Невозможно сделать назначение работы вообще.Причина в том, что присваивание ковариантно по двум аргументам.Нет никакого способа гарантировать, что источник и цель имеют один и тот же динамический тип, и это необходимо для назначения для работы.Если источник слишком велик, часть его обрезается.Гораздо хуже, если цель слишком большая, некоторые из них никогда не будут назначены.Поэтому не имеет значения, на какой объект (цель или источник) вы отправляете: он просто не может работать.Единственное назначение, которое может работать, это не виртуальное, основанное на статическом типе.Это может быть как над, так и под срезом, но, по крайней мере, проблема статична.

Клонирование работает, потому что это функция с одним аргументом (а именно, самообъектом).Как правило, если вы используете «объектные вещи», а не «полезные вещи», то, поскольку вы можете реально манипулировать только значениями, вы должны использовать указатели.В этом случае clone () и друзья - это то, что вам нужно: вы можете назначить указатель просто отлично!

1 голос
/ 06 декабря 2010

Есть две проблемы: одна о двойном копировании в часть A и одна о виртуальном назначении op.

Виртуальное назначение: не очень хорошая идея, потому что оно переносит обнаружение ошибок из времени компиляции во время выполнения. Просто не надо. Общее решение, когда кажется, что требуется виртуальное назначение (или операция, подобная вашей), состоит в том, чтобы вместо этого реализовать клонирование, виртуальную clone функцию-член, которая создает динамически распределенную копию.

Двойное копирование: простой ответ состоит в том, чтобы выразить назначение с точки зрения конструкции. Идиоматический способ сделать это известен как «идиома своп». Проще говоря, создайте копию, затем поменяйте местами ее содержимое с текущим экземпляром, а затем деструктор созданного вами экземпляра позаботится об очистке.

Приветствия & hth.,

0 голосов
/ 06 декабря 2010

deep_copy_from не является ко-вариантом, вы можете использовать ковариацию только в возвращаемых типах.

Вы можете получить предупреждения «Перегрузка скрывает виртуальную функцию» с кодом, который вы написали.

Так как нет способа вызвать версию B или C, не вызывая версию A, поэтому, если вы не измените B или C, вы не сможете избежать вызова версии A дважды, если вам придется вызывать обе версии B и C.

Учитывая, что B и C оба наследуют виртуальные от A, вы, вероятно, не должны заставлять их вызывать версию A, так как последний класс отвечает за часть A.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...