Доступ к защищенному члену объекта члена суперкласса - элегантное решение - PullRequest
4 голосов
/ 30 марта 2012

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

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

#include <iostream>

class A{
protected:
    int var;
    std::vector <double> heavyVar;
public:
    A() {var=1;}
    virtual ~A() {}
    virtual void func() {
        std::cout << "Default behavior" << this->var << std::endl;
    }

    // somewhere along the way, heavyVar is filled with a lot of stuff
};

class B: public A{
protected:
    A* myA;
public:
    B(A &a) : A() {
        this->myA = &a;
        this->var = this->myA->var;
             // copy some simple data, e.g. flags
             // but don't copy a heavy vector variable
    }
    virtual ~B() {}
    virtual void func() {
        this->myA->func();
        std::cout << "This class is a decorator interface only" << std::endl;
    }
};

class C: public B{
private:
    int lotsOfCalc(const std::vector <double> &hv){
        // do some calculations with the vector contents
    }
public:
    C(A &a) : B(a) {
        // the actual decorator
    }
    virtual ~C() {}
    virtual void func() {
        B::func(); // base functionality
        int heavyCalc = lotsOfCalc(this->myA->heavyVar); // illegal
            // here, I actually access a heavy object (not int), and thus
            // would not like to copy it
        std::cout << "Expanded functionality " << heavyCalc << std::endl;
    }
};

int main(void){
    A a;
    B b(a);
    C c(a);
    a.func();
    b.func();
    c.func();
    return 0;
}

Причина для этого заключается в том, что я на самом деле пытаюсь реализовать Pattern Decorator (class B имеет внутреннюю переменную myA, которую я хочу декорировать), но я также хотел бы использовать некоторые из защищенных членов class A при выполнении «декорированных» вычислений (в class B и всехиз его подклассов).Следовательно, этот пример не является правильным примером декоратора (даже не простого).В примере я сосредоточился только на демонстрации проблемной функциональности (что я хочу использовать, но не могу).В этом примере используются даже не все классы / интерфейсы, необходимые для реализации шаблона Decorator (у меня нет абстрактного интерфейса базового класса , также унаследованного конкретными экземплярами базового класса как абстрактный интерфейс декоратора , который будет использоваться в качестве суперкласса для бетонных декораторов ).Я упоминаю декораторы только для контекста (причина, по которой я хочу указатель A*).

В этом конкретном случае я не вижу особого смысла делать (мой эквивалент) int var публичным (илидаже написание общедоступного метода получения) по двум причинам:

  • , более очевидная причина: я не хочу, чтобы пользователи фактически использовали информацию напрямую (у меня есть некоторые функции, которыевернуть информацию, относящуюся и / или записанную в моих protected переменных, но не в само значение переменной)
  • переменная protected в моем случае гораздо труднее скопировать, чем int (это2D std::vector из double с), и копирование его в экземпляр производного класса было бы излишне затратным по времени и памяти

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

Что у меня есть и почему мне это не нравится:

1.объявляя все (соответствующие) унаследованные классы friend s базовому классу:

class A{
    ....
    friend class B;
    friend class C;
};

Мне не нравится это решение, потому что оно заставит меня изменить мой базовый класс каждый раз, когда я пишу новый класс подкласса , и это именно то, чего я пытаюсь избежать.(Я хочу использовать только интерфейс «А» в основных модулях системы.)

2.приведение указателя A* к указателю унаследованного класса и работа с ним

void B::func(){
    B *uglyHack = static_cast<B*>(myA);
    std::cout << uglyHack->var + 1 << std::endl;
}

Имя переменной довольно наводит на размышления относительно моих ощущений от использования этого подхода, но именно яиспользуя прямо сейчас.Поскольку я проектировал эти классы, я знаю, как быть осторожным и использовать только то, что фактически реализовано в class A, рассматривая его как class B.Но если кто-то еще продолжит работу над моим проектом, он может быть не очень знаком с кодом.Кроме того, приведение указателя переменной к чему-то, что я очень хорошо знаю, что это не просто чувствует чистое зло для меня.

Я пытаюсь сделать код этого проекта приятным ипродуман до мелочей, поэтому, если у кого-то есть какие-либо предложения относительно решения, которое время от времени не требует изменения базового класса или использования злых понятий, я был бы очень признателен.

Ответы [ 3 ]

2 голосов
/ 30 марта 2012

Я верю, что вы, возможно, захотите пересмотреть дизайн, но решение конкретного вопроса как я могу получить доступ к члену? может быть:

class A{
protected:
    int var;

    static int& varAccessor( A& a ) {
       return a.var;
    }
};

А потомв производном типе вызовите защищенный метод доступа, передав объект-член по ссылке:

varAccessor( this->myA ) = 5;

Теперь, если вы думаете о шаблоне декоратора, я не думаю, что это путь.Источником путаницы является то, что большинство людей не осознают, что тип имеет два отдельных интерфейса: интерфейс public для пользователей и интерфейс virtual для поставщиков реализации (т. Е. Производных типов), поскольку во многих случаях обе функции являются public и virtual (т. Е. Язык позволяет связывать два семантически различных интерфейса).В шаблоне Decorator вы используете базовый интерфейс для обеспечения реализации.Существует наследование, так что производный тип может обеспечить операцию для пользователя посредством некоторой фактической работы (декорирования) и последующей передачи работы фактическому объекту.Отношения наследования не доступны для доступа к объекту реализации каким-либо образом через защищенные элементы, и это само по себе опасно.Если вам передают объект производного типа, который имеет более строгие инварианты относительно этого защищенного члена (т. Е. Для объектов типа X, var должно быть нечетным числом), подход, который вы используете, позволит decorator (в некотором роде) ломает инварианты того типа X, который должен быть просто украшен.

0 голосов
/ 03 апреля 2012

Я думаю, что нашел хороший способ сделать то, что я хочу, в структуре наследования, которую я имею.

Во-первых, в классе base (тот, которыйявляется базой для всех других классов, а также интерфейс абстрактного базового класса в шаблоне Decorator), я добавляю объявление friend class only для первого подкласса (тот, которыйбудет действовать как интерфейс абстрактного декоратора ):

class A{

    ....
    friend class B;
};

Затем я добавлю protected функции доступа в подкласс для всех интересных переменных в базовом классе:

class B : public A{

    ...
    protected:
        A *myA;
        int getAVar() {return myA->var;}
        std::vector <double> &getAHeavyVar {return myA->heavyVar;}
};

И, наконец, я могу получить доступ ко всем нужным вещам из всех классов, которые наследуют class B (те, которые будут бетонные декораторы ) контролируемым образом (в отличие от static_cast<>) через функцию доступа без необходимости сделать все подклассы B friend s из class A:

class C : public B{

    ....
    public:
        virtual void func() {
        B::func(); // base functionality
        int heavyCalc = lotsOfCalc(this->getAHeavyVar); // legal now!
            // here, I actually access a heavy object (not int), and thus
            // would not like to copy it
        std::cout << "Expanded functionality " << heavyCalc << std::endl;
        std::cout << "And also the int: " << this->getAVar << std::endl;
            // this time, completely legal
    }
};

Я также пыталсядать только определенные функции в class B доступе друга (объявив их как функции друзей ), но это не сработало, поскольку мне нужно было объявить функции внутри class B до объявления друга в class A.Поскольку в этом случае class B наследует class A, это дало бы мне циклическую зависимость ( прямое объявление из class B недостаточно для использования только функций-друзей, но оно прекрасно работает для объявления класса-друга).

0 голосов
/ 30 марта 2012

Я не могу найти никаких примеров использования шаблона декоратора таким образом.Похоже, что в C ++ он используется для оформления, а затем делегирования обратно общедоступному абстрактному интерфейсу decoratee, а не доступа к нему из непубличных членов.

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

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