Подклассы друзей C ++ имеют доступ к закрытым членам (шаблон стратегии) - PullRequest
0 голосов
/ 03 января 2019

Я кодировал метод кроссовера для генетического алгоритма (см. https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)).

Метод кроссовера модифицирует закрытые члены класса Chromosome, но я вытащил его из Chromosome в отдельный чистый виртуальный базовый класс CrossoverStrategy (друг Chromosome), чтобы каждый метод кроссовера был красиво инкапсулирован в подкласс, то есть шаблон стратегии GoF. (см. https://en.wikipedia.org/wiki/Strategy_pattern).

Теперь проблема в том, что подклассы CrossoverStrategy не могут получить доступ к закрытым членам Chromosome, потому что дружба не наследуется в C ++. Я вижу только 2 решения:

1) Добавить методы доступа к чисто виртуальному базовому классу, например. CrossoverStrategy :: getGenes (), чтобы сделать частные элементы Chromosome доступными для подклассов. Поскольку CrossoverStrategy не может предвидеть все, что его подклассы могут захотеть сделать с Хромосомой, мне нужно разоблачить все заранее. Гадкий!

2) Форвард объявляет каждый подкласс CrossoverStrategy и явно делает его другом Chromosome. Это выглядит немного менее уродливо, по крайней мере, сохраняет интерфейс и код чище Я склоняюсь к этому варианту для эстетики.

Какие-нибудь лучшие дизайнерские предложения? Код ниже.

// Chromosome.h ++++++++++++++++++++++++++++++++++++++++++++++++

class CrossoverStrategy
{
public:
    virtual std::vector<Chromosome*> crossover(Chromosome *parent1, Chromosome *parent2) = 0;
    const std::vector<double> &getGenes(Chromosome *instance) { return instance != NULL ? instance->m_genes : std::vector<double>(); }; // OPTION #1 ... BOO! UGLY!
};

class CrossoverStrategyExample1; // OPTION #2 ... BOO! UGLY!

class Chromosome
{
public:
    // Friends
    friend class CrossoverStrategy;
    friend class CrossoverStrategyExample1; // OPTION #2 ... BOO! UGLY!
private:
    std::vector<double> m_genes;
};

// CrossoverStrategies.h ++++++++++++++++++++++++++++++++++++++++++++++++

#include "Chromosome.h"

class CrossoverStrategyExample1 : public CrossoverStrategy
{
public:
    virtual std::vector<Chromosome*> crossover(Chromosome *parent1, Chromosome *parent2);
private:
};

// CrossoverStrategies.cpp ++++++++++++++++++++++++++++++++++++++++++++++++

#include "CrossoverStrategies.h"

std::vector<Chromosome*> CrossoverStrategyExample1::crossover(Chromosome *parent1, Chromosome *parent2)
{
    // Do something with Chromosome private members
    // PROBLEM ... m_genes not accessible to subclasses? BOO BOO BOO!
    (for unsigned long i = 0; i < parent1->m_genes.size(); i++)
        parent1->m_genes[i] = 0.0;
}

Ответы [ 3 ]

0 голосов
/ 03 января 2019

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

. Второй вариант для Chromosome - предоставить общедоступный метод получения и установки длязатронутые данные, такие как;

 class Chromosome
 {
      public:

          std::vector<double> getGenes() const {return m_genes;};
          bool setGenes(const std::vector<double> &newgenes)
          {
               bool is_error = true;
               if (IsValid(newgnes))
               {
                    is_error = false;
                    m_genes = newgenes;
               }
               return is_error;    //  return true if change is rejected
          };

       private:

           std::vector<double> m_genes;
 };

Тогда все, что нужно сделать CrossOverStrategy и его производным классам, учитывая действительные указатели на Chromosome s, это запросить гены, сделать все, что нужно, и (когда закончите) предоставьте новый набор генов обратно выбранному Chromosomes.

Инкапсуляция Chromosome сохраняется различными мерами, поскольку единственный способ изменить гены - через функцию-член Chromosome, т.е.нет никакого способа изменить гены в хромосоме вне контроля класса Chromosome.Что позволяет Chromosome делать любые проверки, которые ему нравятся, и отвергать плохие гены, если это необходимо.

Нет необходимости в том, чтобы другой класс или функция были другом Chromosome.Основным преимуществом является то, что нет необходимости изменять класс Chromosome всякий раз, когда новый класс является производным от CrossOverStrategy.

Компромисс состоит в том, что гены извлекаются и изменяются путем копирования полного набора (потенциальное снижение производительности при копировании).Но это избавляет от необходимости нарушать инкапсуляцию класса Chromosome, предоставляя, прямо или косвенно, ссылку на его закрытые члены для любых других классов.

Если копирование полного набора хромосом плохо,разработать некоторые дополнительные функции-члены Chromosome, которые позволяют вызывающей стороне запрашивать частичные изменения (например, обновлять определенные гены, вставлять набор генов в указанное место в векторе генов и т. д.).Эти дополнительные функции должны работать по тому же принципу: все изменения генов в Chromosome проходят через функции-члены Chromosome, и нет никакого механизма «задней двери», чтобы другой код мог пропустить изменения.

Если вы действительно хотите, вы можете сделать установщик и получатель private членами Chromosome и сделать только базовый класс CrossOverStrategy a friend.Тогда все, что нужно CrossOverStrategy, - это предоставить protected помощников, которые вызывают только частных помощников Chromosome.

class CrossoverStrategy
{
    public:
       virtual std::vector<Chromosome*> crossover(Chromosome *parent1, Chromosome *parent2) = 0;

    protected:

        std::vector<double> getGenes(Chromosome *instance)
        {
           return instance ? instance->getGenes() : std::vector<double>();
        };

        bool setGenes(Chromosome *instance, const std::vector<double> &newgenes)
        {
           return instance ? instance->setGenes(newgenes)) : true;  // true indicates error
        };
};

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

0 голосов
/ 03 января 2019

Ваша идея в корне ошибочна.

С одной стороны, вы говорите, что не хотите, чтобы только кто-либо мог связываться с вектором генов.

С другой стороны, вы хотите, чтобы любой потомок из CrossoverStrategy мог связываться с вектором генов.

Но есть противоречие. «Любой потомок» класса - это «просто кто угодно». Любой пользователь может наследовать от любого класса и делать то, что он хочет с вашим вектором генов. Им нужно только пройти небольшое одноразовое неудобство наследования от CrossoverStrategy.

Это причина, по которой дружба в C ++ не наследуется. Если бы это было так, контроль доступа был бы бесполезен в присутствии классов друзей.

Конечно, вы можете симулировать наследственную дружбу, имея защищенного добытчика в CrossoverStrategy, как предполагает один из ответов. Но это противоречит цели контроля доступа. Это делает массивы генов такими же хорошими, как публичные.

0 голосов
/ 03 января 2019

Вариант 2 следует отклонить, поскольку он не масштабируется.Вы будете постоянно изменять Chromosome, чтобы обновлять его до новых CrossoverStrategies.

Вариант 1 является странной идеей, поскольку он помещает функцию получения для элементов данных Chromosome вне Chromosome.Я вижу некоторые случаи, когда это привлекательная идея, если getGenes защищен, но я не уверен в этом.Вместо этого рассмотрим

Вариант 1-A

class Chromosome
{
public:
    const std::vector<double>& getGenes() const
    {
        return m_genes;
    }
private:
    std::vector<double> m_genes;
};

Каждый, кто может получить доступ к Chromosome, может получить доступ к getGenes, но они не могут ничего сделать, чтобы повредить его, и Chromosome остаетсяблаженно неосведомлен о своих пользователях.

Вариант 3: Используйте Идиома Pimpl

Короткий и глупый пример с несколькими недостатками, чтобы сделать демонстрацию короткой

Chromosome.h ++++++++++++++++++++++++++++++++++++++++++++++++

#include <vector>
class Chromosome; // forward declaration only
class CrossoverStrategy
{
public:
    virtual ~CrossoverStrategy() = default;
    virtual std::vector<Chromosome*> crossover(Chromosome *parent1, Chromosome *parent2) = 0;
};

Chromosome * ChromosomeFactory(/* some construction parameters here */);
// should also provide a declaration of a factory function to provide CrossoverStrategies

CrossoverStrategies.h +++++++++++++++++++++++++++++++++++++++++++++++++

#include "Chromosome.h"

class CrossoverStrategyExample1 : public CrossoverStrategy
{
public:
    virtual std::vector<Chromosome*> crossover(Chromosome *parent1, Chromosome *parent2);
private:
};

CrossoverStrategies.cpp ++++++++++++++++++++++++++++++++++++++++++++++++

#include "CrossoverStrategies.h"
class Chromosome
{
public:
    std::vector<double> m_genes;

    // silence a warning
    Chromosome(): m_genes{}
    {

    }
};

// Because Chromosome is only defined in this file, only this file can use the internals 
// of Chromosome. They are public, but the outside world doesn't know that 

Chromosome * ChromosomeFactory(/* some construction parameters here */)
{
    // Probably makes and returns a pointer to a Chromosome,
    // but could pull it from a list, copy construct from a template, etc...
    return new Chromosome(/* some construction parameters here */);
}

// should also provide a definition of a factory function to provide CrossoverStrategies


std::vector<Chromosome*> CrossoverStrategyExample1::crossover(Chromosome *parent1,
                                                              Chromosome *parent2)
{
    for (unsigned long i = 0; i < parent1->m_genes.size(); i++)
        parent1->m_genes[i] = 0.0;
    return std::vector<Chromosome*>{}; // silence a warning
}

Main.cpp ++++++++++++++++++++++++++++++++++++++++++++++++

#include "Chromosome.h"
#include "CrossoverStrategies.h" // A bad idea. Forces recompilation when strategies are added

int main()
{
    Chromosome * p1 = ChromosomeFactory(/* some construction parameters here */);
    p1->m_genes.push_back(0.0); // will fail to compile (incomplete type)
    Chromosome * p2 = ChromosomeFactory(/* some construction parameters here */);

    // probably should hide the next line with a factory as well
    CrossoverStrategy * strategy = new CrossoverStrategyExample1();
    strategy->crossover(p1, p2);
}

Краткое послание о безопасности.Это всегда приходит за плату.Как правило, это делает вещи сложнее в использовании.Это делает их более сложными для злоумышленника, но также усложняет работу законных пользователей.Стоит это того или нет, решать только вам.

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