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

C++ friend subclasses access private members (strategy pattern)

我已经编写了遗传算法的交叉方法(参见 https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm))。

交叉方法修改了Chromosome的私有成员class但是我把它从Chromosome中拉出来放到一个单独的纯虚拟基础classCrossoverStrategy(Chromosome的朋友)很好地保持每个交叉方法封装在子class中,即GoF策略模式(参见https://en.wikipedia.org/wiki/Strategy_pattern)。

现在的问题是 CrossoverStrategy subclasses 无法访问染色体私有成员,因为友谊在 C++ 中不被继承。我看到的唯一 2 个解决方案是:

1) 将访问器方法添加到纯虚拟基 class 例如CrossoverStrategy::getGenes() 使子 classes 可以访问染色体私有成员。因为 CrossoverStrategy 无法预测它的子classes 可能想用 Chromosome 做的所有事情,所以我需要预先公开所有内容。丑!

2) 转发声明每个 CrossoverStrategy subclass 并明确使其成为 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;
}

应拒绝选项 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);
}

关于安全的简短后记。它总是要付出代价的。通常它会使事情更难使用。这让攻击者更难,但也让合法用户更难。值不值由你决定

第一个明显的选项是考虑 Chromosome 的成员是否应该是 public。鉴于您希望任意数量的 classes 访问其数据,一个明显的选择是使该数据 public.

第二种选择是 Chromosome 为受影响的数据提供 public getter 和 setter,例如;

 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 及其派生的 classes 需要做的,给出指向 Chromosomes 的有效指针,请求基因,做任何需要的事情,并且(完成后) 提供一组新的基因返回给选定的 Chromosomes.

Chromosome 的封装通过各种措施得以保留,因为改变基因的唯一方法是通过 Chromosome 的成员函数,即无法改变染色体中不受控制的基因Chromosome class。这允许 Chromosome 进行任何它喜欢的检查,并在需要时拒绝不良基因。

不需要任何其他 class 或函数成为 Chromosome 的友元。一个关键的优点是,每当从 CrossOverStrategy 派生出新的 class 时,都不需要更改 Chromosome class。

权衡是通过复制完整集来检索和更改基因(复制的潜在性能影响)。但它通过直接或间接地向任何其他 classes.classes.

提供对其私有成员的引用,避免了打破 Chromosome class 封装的需要

如果复制整套染色体是一件坏事,请计算出 Chromosome 的一些附加成员函数,允许调用者请求部分更改(例如更新特定基因,将一组基因插入基因载体中指定的位置等)。这些附加函数需要按照相同的原则工作:Chromosome 中的所有基因更改都通过 Chromosome 的成员函数进行,并且没有 "back door" 机制让其他代码偷偷更改.

如果你真的想要,你可以让 setter 和 getter private 成为 Chromosome 的成员,并且只让基础 class 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 派生的 classes 可以访问 protected 助手。如果 Chromosome 的工作方式发生变化,那么在这种情况下唯一需要调整的 class 是基础 CrossOverStrategy class - 因为它的派生 classes根本不要(直接)访问 Chromosome

你的想法存在根本性缺陷。

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

另一方面,您希望 CrossoverStrategy 任何后代 能够扰乱基因向量。

但是有一个矛盾。一个 class 的 "Any descendant" 是 "just anyone"。任何用户都可以从任何 class 继承并使用您的基因向量做他们想做的事。他们只需要经历从 CrossoverStrategy.

继承的一次性小麻烦

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

当然,如答案之一所示,您可以通过在 CrossoverStrategy 中设置受保护的 getter 来模拟可继承的友谊。但这样做违背了访问控制的目的。它使基因阵列与 public.

一样好