Проблема свопинга в C ++ в сценарии наследования - PullRequest
3 голосов
/ 01 декабря 2009

Я хочу добавить функциональность подкачки к двум существующим классам C ++. Один класс наследует от другого. Я хочу, чтобы экземпляры каждого класса можно было заменять только экземплярами одного и того же класса. Для полубетона, скажем, у меня есть классы Foo и Bar. Бар наследует от Foo. Я определяю Foo :: swap (Foo &) и Bar :: swap (Bar &). Bar :: swap делегаты Foo :: swap. Я хочу, чтобы Foo :: swap работал только на экземплярах Foo, а Bar :: swap работал только на экземплярах Bar: я не могу понять, как применить это требование.

Вот пример того, что доставляет мне неприятности:

#include <algorithm>
#include <iostream>

struct Foo {
    int x;
    Foo(int x) : x(x) {};

    virtual void swap(Foo &other) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        std::swap(this->x, other.x);
    };
};

struct Bar : public Foo {
    int y;
    Bar(int x, int y) : Foo(x), y(y) {};

    virtual void swap(Bar &other) {
        std::cout << __PRETTY_FUNCTION__ << " ";
        Foo::swap(other);
        std::swap(this->y, other.y);
    };
};

void display(Foo &f1, Foo &f2, Bar &b34, Bar &b56)
{
    using namespace std;

    cout << "f1:  " << f1.x                  << endl;
    cout << "f2:  " << f2.x                  << endl;
    cout << "b34: " << b34.x << " " << b34.y << endl;
    cout << "b56: " << b56.x << " " << b56.y << endl;
}

int main(int argc, char **argv)
{
    {
        Foo f1(1), f2(2);
        Bar b34(3,4), b56(5,6);
        std::cout << std::endl << "Initial values: " << std::endl;
        display(f1,f2,b34,b56);
    }

    {
        Foo f1(1), f2(2);
        Bar b34(3,4), b56(5,6);
        std::cout << std::endl << "After Homogeneous Swap: " << std::endl;
        f1.swap(f2);             // Desired
        b34.swap(b56);           // Desired
        display(f1,f2,b34,b56);
    }

    {
        Foo f1(1), f2(2);
        Bar b34(3,4), b56(5,6);
        std::cout << std::endl << "After Heterogeneous Member Swap: " << std::endl;
        // b56.swap(f2);         // Doesn't compile, excellent
        f1.swap(b34);            // Want this to not compile, but unsure how
        display(f1,f2,b34,b56);
    }

    return 0;
}

Вот вывод:

Initial values: 
f1:  1
f2:  2
b34: 3 4
b56: 5 6

After Homogeneous Swap: 
virtual void Foo::swap(Foo&)
virtual void Bar::swap(Bar&) virtual void Foo::swap(Foo&)
f1:  2
f2:  1
b34: 5 6
b56: 3 4

After Heterogeneous Member Swap: 
virtual void Foo::swap(Foo&)
f1:  3
f2:  2
b34: 1 4
b56: 5 6

Вы можете видеть в окончательной группе вывода, где f1.swap (b34) «нарезал» b34 потенциально неприятным способом. Я бы хотел, чтобы виновная строка не компилировалась или не взорвалась во время выполнения. Из-за вовлеченного наследования, я думаю, я столкнусь с той же проблемой, если использую реализацию подкачки не-члена или друга.

Код доступен по адресу codepad , если это поможет.

Этот вариант использования возникает потому, что Я хочу добавить swap для boost :: multi_array и boost :: multi_array_ref . multi_array наследуется от multi_array_ref. Имеет смысл только менять multi_arrays на multi_arrays и multi_array_refs на multi_array_refs.

Ответы [ 5 ]

5 голосов
/ 01 декабря 2009

Поменяйте местами, как присваивание и сравнение хорошо работают с типами значений и плохо работают с основами иерархий классов.

Я всегда считал, что проще всего следовать рекомендации Скотта Мейера «Эффективный С ++», чтобы не выводить из конкретных классов и не конкретизировать только листовые классы. Затем вы можете безопасно реализовать swap, operator == и т. Д. Как не виртуальные функции только для конечных узлов.

Хотя возможно иметь виртуальные функции подкачки, суть виртуальных базовых классов в том, чтобы иметь динамическое поведение во время выполнения, поэтому я думаю, что вы проиграли, пытаясь получить все некорректные возможности для сбоя во время компиляции. 1005 *

Если вы хотите пойти по пути виртуального свопинга, то один из возможных подходов - сделать что-то вроде этого.

class Base
{
public:
    virtual void Swap(Base& other) = 0;
};

class ConcreteDerived
{
    virtual void Swap(Base& other)
    {
        // might throw bad_cast, in this case desirable
        ConcreteDerived& cother = dynamic_cast<ConcreteDerived&>(other);bad_cast
        PrivateSwap(cother);
    }

    void PrivateSwap(ConcreteDerived& other)
    {
        // swap implementation
    }
};
3 голосов
/ 01 декабря 2009

(несколько хакерское решение)

Добавьте защищенный виртуальный метод isBaseFoo (), сделайте так, чтобы он возвращал true в Foo, и false в Bar, метод swap для Foo может проверить, что его аргумент имеет isBaseFoo () == true.

Зло, и обнаруживает проблему только во время выполнения, но я не могу придумать ничего лучшего, хотя ответ Чарльза Бэйли может быть лучше, если вы разрешите dynamic_cast <>.

2 голосов
/ 01 декабря 2009

Вы действительно не можете этого сделать, но я все равно не вижу смысла. Это не хуже, чем нарезка на operator= или копирование конструкторов, и вы не можете избежать последнего. Почему swap должно быть иначе?

По той же причине, вероятно, не стоит делать swap виртуальным, по той же причине, по которой вы не делаете operator= виртуальным.

1 голос
/ 27 сентября 2015

Я думаю, что этот сценарий кейса теперь решается наличием семантики перемещения в C ++ 11. Я обычно использую функцию подкачки только для того, чтобы избежать дублирования копирования в назначениях, поэтому она используется только статически, производным классом, которому необходимо расширить функцию подкачки и который знает статический тип своей базы, поэтому нет необходимости в виртуальности ( было сказано, что это может привести к тонким проблемам с нарезкой). Фактически я объявляю функцию подкачки как защищенный метод в моей базе, чтобы убедиться, что она не используется напрямую где-либо еще. В этом случае наиболее конкретный класс может иметь общедоступную окончательную версию.

0 голосов
/ 01 декабря 2009

То, что вы на самом деле пытаетесь сделать, это поменять экземпляры классов из иерархии наследования третьей стороны. Учитывая это, я забуду об использовании свопа на реальных классах и добавлю уровень косвенности. Использование boost :: shared_ptr - хороший подход; используйте экземпляры shared_ptr, содержащие любой класс, который вам нужен, и поменяйтесь на все, что душе угодно.

В общем, решить проблему так, как вы ее задали во время компиляции, сложно по всем причинам, описанным другими ответчиками.

...