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 需要做的,给出指向 Chromosome
s 的有效指针,请求基因,做任何需要的事情,并且(完成后) 提供一组新的基因返回给选定的 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.
一样好
我已经编写了遗传算法的交叉方法(参见 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 需要做的,给出指向 Chromosome
s 的有效指针,请求基因,做任何需要的事情,并且(完成后) 提供一组新的基因返回给选定的 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.