C ++朋友子类访问私有成员(策略模式)

时间:2019-01-03 02:05:38

标签: c++ inheritance design-patterns friend

我已经为遗传算法编码了一种交叉方法(请参见https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm))。

crossover方法修改了Chromosome类的私有成员,但是我将其从Chromosome中抽出到了一个单独的纯虚拟基类CrossoverStrategy(Chromosome的朋友)中,以将每个crossover方法很好地封装在一个子类中,即GoF策略模式(请参阅https://en.wikipedia.org/wiki/Strategy_pattern)。

现在的问题是,CrossoverStrategy子类无法访问Chromosome私有成员,因为友谊不是在C ++中继承的。我看到的仅有的两种解决方案是:

1)将访问器方法添加到纯虚拟基类中,例如CrossoverStrategy :: getGenes()使子类可以访问染色体私有成员。因为CrossoverStrategy不能预期其子类可能希望与Chromosome一起完成的所有工作,所以我需要预先公开所有内容。丑!

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 :(得分:2)

选项2应该被拒绝,因为它不能缩放。您将不断修改Chromosome,以使其与新的CrossoverStrategies保持同步。

选项1是一个奇怪的主意,因为它将Chromosome的数据成员的getter函数放在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:使用The Pimpl Idiom

简短的例子,但有一些缺陷,使演示很简短

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

关于安全性的快速后记。它总是要付出代价的。通常,它使事情变得更难使用。这使他们对攻击者而言更加困难,但对于合法用户而言也更加困难。值得与否取决于您。

答案 1 :(得分:2)

第一个显而易见的选择是考虑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及其派生类都需要做的工作是请求基因,执行所需的任何操作,并(在完成时)提供一组新的基因返回所选的Chromosomes

Chromosome的封装通过各种措施得以保留,因为改变基因的唯一途径是通过Chromosome的成员函数,即无法改变{ {1}}类。这样Chromosome可以进行任何喜欢的检查,并根据需要拒绝不良基因。

不需要其他任何类或函数成为Chromosome的朋友。一个关键优点是,每当从Chromosome派生一个新类时,就不必更改Chromosome类。

需要权衡的是通过复制完整的基因组来获取基因并对其进行更改(复制的潜在性能影响)。但是,通过直接或间接提供对其他任何类的私有成员的引用,可以避免破坏CrossOverStrategy类的封装。

如果复制完整的染色体组是一件坏事,请计算出Chromosome的一些其他成员函数,这些函数允许调用者请求部分更改(例如,更新特定基因,将一组基因插入指定位置)在基因的载体中,等等)。这些附加功能需要遵循相同的原理:Chromosome中基因的所有更改都将通过Chromosome的成员函数进行,并且没有“后门”机制可以让其他代码偷偷通过更改。

如果您确实需要,可以将Chromosome的setter和getter private的成员设为Chromosome的基类。然后,CrossOverStrategy所要做的就是提供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全部。

答案 2 :(得分:0)

您的想法从根本上是有缺陷的。

一方面,您说您不希望任何人都能弄乱基因载体。

另一方面,您希望CrossoverStrategy任何后代能够弄乱基因的载体。

但是有一个矛盾。类的“任何后代”是“只是任何人”。任何用户都可以从任何类别继承并使用您的基因载体做他们想要的事情。他们只需要经历一次短暂的从CrossoverStrategy继承的不便。

这就是为什么不继承C ++友谊的原因。如果是这样,那么在存在朋友类的情况下,访问控制将毫无用处。

当然,您可以通过在CrossoverStrategy中使用受保护的getter来模拟可继承的友谊,如答案之一所示。但是这样做会破坏访问控制的目的。它使基因的排列与公共的一样好。