提问者:小点点

静态成员效用函数与非成员效用函数


我在这里读了一些其他问题,但没有找到任何明确的答案。

背景

作为类比,假设有一个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

您应该如何决定如何公开与类相关的函数? 特别是将函数公开为:

  1. 常规成员函数。
  2. 静态成员函数。
  3. 使用类实例的自由函数。
  4. 其他内容?

共3个答案

匿名用户

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