我在这里读了一些其他问题,但没有找到任何明确的答案。
背景
作为类比,假设有一个node类,并且我想从这个类中得到连接节点的图。 为此,我需要一个函数get_graph()
。
第一次尝试:
该函数是node
类的常规成员函数:
class Node
{
//impl...
GraphObj get_graph();
}
我不喜欢这个,因为图不是node类的功能。 它实际上是对node
的一些实例的查询。
第二次尝试:
该函数是node
类的静态成员函数:
class Node
{
//impl...
static GraphObj get_graph();
}
这更好,因为现在我已经将函数与实例解耦。 但是,我仍然不觉得这个函数是类的一部分。
第三次尝试:
该函数是同一命名空间中的空闲函数/tu:
class Node
{
//impl...
}
GraphObj get_graph(Node* graph_source);
现在真的脱钩了! 我不希望这个类的用户错过这个功能。 还有,这个函数只能作用在这个类上,所以会不会太解耦了?
DR
您应该如何决定如何公开与类相关的函数? 特别是将函数公开为:
ScottMeyer关于成员函数与自由函数的文章与此高度相关;
如果您编写的函数既可以实现为成员,也可以实现为非朋友非成员,那么您应该倾向于将其实现为非成员函数。 这个决定增加了类封装。
查看这篇文章以获得解释。
成员函数是否应该是静态
的问题是另一个问题。 通常,如果它们没有耦合到特定实例,则将它们设置为statice
。 示例是用于根据构造函数参数设置对象状态的实用函数,例如。
A::A(int someArg) :
dataMember{computeInitialState(someArg)}
这里,ComputeInitialState
应该是statice
。 另一个例子是命名的构造函数,例如point::cartesian(double x,double y)
-这也应该是static
。
你有一个很好的问题,我必须说类比也很好。 我将尝试解释何时使用什么,然后在最后,我们将研究适合这个用例的内容。
何时使用常规成员函数?
您可能会避免使用它,因为它们通常不起作用(在下一节中解释)。 唯一的例外是构造函数。 只有构造函数在对象级别工作(用一个值来初始化它),因此它们大多在类级别,本质上是非静态的。
何时使用静态成员函数?
给定一个类a
,如果只有类a
的对象需要该方法,则应将它们定义为静态成员。 假设有一个isValid()
方法。 它只接受一个对象,并根据该对象是否具有所有有效值返回一个布尔值。 此类方法应定义为类中的静态方法,因为有效性的定义可能远远超出NULL和空检查。 下面是一个例子
class A {
int n1; // <-- Must be greater than 0
static bool isValid(A ob) {
if (ob != NULL && ob.n1 != null && ob.n1 > 0) return true;
return false;
}
}
何时使用非成员方法?
如果您有一个方法可以在多个类的对象上工作,那么它们应该被定义为非成员方法。 这方面的一个示例是GetValueOrDefault()
方法,它的工作方式如下所定义
template<typename T>
T getValueOrDefault(T n1, T n2) {
return n1 == NULL ? n2 : n1;
}
int main () {
int num1 = NULL, num2 = 2;
std::cout << getValueOrDefault(num1, 10); // <-- Will return 10 as num1 is NULL
std::cout << getValueOrDefault(num2, 10); // <-- Will return 2 as num2 is NOT NULL
}
回到你的图表类比。
最好将该方法移出node类,并创建另一个Graph
类,该类中可能包含一个方法GetGraph
。 如果在所有类中遵循某些标准,则可以将该方法泛化为不仅使用node
类,而且使用任何链接列表类型的类。
否则,即使是静态方法也可以正常工作,但它可能会降低类中的内聚性。
我会和模板朋友一起做。
在任何情况下,如果您想要通用的东西,您需要有某种通用接口,无论是相同的成员名称还是访问函数。
main.hh(或get_graph.hh)
//
#include <iostream>
template<typename T>
T& get_graph (T& obj)
{
std::cout << obj.getInternalData() << std::endl;
// this could be obj.data directly if the member names are always the same
return obj;
}
main.cc
#include "main.hh"
class INode
{
private:
virtual int getInternalData () = 0;
};
class Node : private INode {
public:
Node(int i) : data (i) {}
int getValue () { return data; }
private:
int data;
int getInternalData () { return data; }
template<typename T>
friend T& get_graph (T&);
};
class NodeB : private INode {
public:
NodeB(int i) : someother (i) {}
int getValue () { return someother; }
private:
int someother;
int getInternalData () { return someother; }
template<typename T>
friend T& get_graph (T&);
};
int main()
{
Node obj (999);
Node & n = get_graph(obj);
NodeB obk (111);
NodeB & m = get_graph(obk);
std::cout << "Data: " << n.getValue() << std::endl;
std::cout << "Data: " << m.getValue() << std::endl;
}
在这种情况下,访问私有成员的函数是私有的,因此您将其与外部用户隔离开来,他们可以在不知情的情况下简单地使用它。
该模板允许您处理符合INode接口的任何节点。
如果您向节点
类添加相关操作符,您甚至不需要该接口:实现操作符的每个“graph getable”类都将使用模板get_graph
。