C++中的结构体和成员函数 vs. 具有公共变量的类

30

这实际上是关于良好形式/最佳实践的问题。我在C++中使用结构体来形成对象,这些对象旨在基本上保存数据,而不是制作具有大量get/set值方法但除此之外无任何作用的类。例如:

struct Person {
    std::string name;
    DateObject dob;
    (...)
};

如果你想象一下还有20个变量,那么将其写成一个带有私有成员和40多个访问器的类会让管理变得很麻烦,并且对我来说似乎有些浪费。

然而,有时候我可能需要给数据添加一些最基本的功能。在这个例子中,例如,假设我有时需要根据出生日期计算年龄:

struct Person {
    std::string name;
    DateObject dob;
    (...)
    int age() {return calculated age from dob;}
}
当然,对于任何复杂的功能,我都会创建一个类,但对于像这样的简单功能,这样做是否是“糟糕的设计”?如果我使用类,将数据变量保留为公共类成员是不好的形式,还是需要接受并创建大量访问器方法的类?我了解类和结构之间的区别,我只是想问最佳实践。

编写访问器方法(getter/setter)是一个好主意。如果你只是简单地谷歌一下,你可能会找到大量关于这个话题的讨论。提示:大多数集成开发环境都提供自动生成getter和setter的功能。 - Sebastian Hoffmann
2
classstruct类型的区别在于,class的默认访问控制是private,而struct的默认访问控制是public。在创建类型之外使用这些关键字可能略有不同...但你似乎在将很多精力投入到structclass这两个词在类型中的区别上。它可以用于特定项目的文档,但这是项目特定的。 - Yakk - Adam Nevraumont
2
还有一个免费函数的选项 int age(const Person& person)。并不是所有的东西都必须是具有方法的对象。 - molbdnilo
可能是https://dev59.com/Q3A75IYBdhLWcg3wSm64?rq=1的重复问题。 - Polar
7个回答

28

我认为在这里考虑两个重要的设计原则:

  1. 如果某个类存在不变式,通过接口隐藏该类的表示。

    当某个类存在不变式时,就存在一些无效状态。该类应始终保持其不变式。

    考虑一个代表2D几何点的Point类型。这只是一个具有公共xy数据成员的struct。不存在无效点。每种xy值的组合都是完全正常的。

    对于Person而言,它是否具有不变式完全取决于手头问题。例如,空名称是否算作有效名称?Person 可以具有任何出生日期吗?对于您的情况,我认为答案是肯定的,因此您的类应该保持成员公开。

    参见:Classes Should Enforce Invariants

  2. 非友元非成员函数可以提高封装性。

    没有理由将age函数实现为成员函数。通过Person的公共接口可以计算出age的结果,因此它不必成为成员函数。将其放置在与Person相同的命名空间中,以便通过参数相关查找找到它。通过ADL找到的函数是该类的接口的一部分;它们只是没有访问私有数据。

    如果您将其设置为成员函数,并且有一天向Person引入了某些私有状态,则会出现不必要的依赖关系。突然之间,age可以访问比它需要的更多的数据。

    参见:How Non-Member Functions Improve Encapsulation

这是我的实现方式:

struct Person {
  std::string name;
  DateObject dob;
};

int age(const Person& person) {
  return calculated age from person.dob;
}

2
非常好的回答,正是我在寻找的信息类型,谢谢! - amnesia
我同意这个答案。我还要补充一点,将age定义为成员函数是控制耦合性较差的做法-调用者必须向该方法传递当前日期才能对其进行良好的理解,或者必须从其他地方检索当前日期,这对调用者来说并不明显。age向客户端接口公开了更多类型实现的细节,而这并不是必需的。如果可以保证此结构永远不会变得更加复杂,那么它可能被保留为成员函数,但即使如此,我也至少会声明它为inline并添加一个currentDate参数。 - Michael Hoffmann
@Joseph Mansfield “将它放在与Person相同的命名空间中”,我们是否期望看到一个封装您代码块的namespace PeopleStuff {...}呢?或者将age()放在同一个文件中就足够了吗? - jigglypuff
将其放置在与Person相同的命名空间中,以便通过参数相关查找找到它。通过ADL找到的函数是该类的接口的一部分;它们只是无法访问私有数据而已。这真是令人惊叹。nobism提出了一个很好的问题,您能否更详细地阐述一下这个问题的具体内容? - Dale

6
在C++中,结构体是类,它们唯一的区别(至少我能想到的)是在结构体中成员默认为public,但在类中它们是private。这意味着使用结构体就像你正在做的那样完全可以接受 - 这篇文章很好地解释了(链接)

1
如果您需要一些数据持有者,则最好选择没有任何get/set方法的结构体。
如果涉及到更多内容,比如这里的“人”。
  1. 它模拟了现实世界中的实体,
  2. 具有明确的状态和行为,
  3. 与外部世界进行交互,
  4. 展示了与其他实体简单/复杂的关系,
  5. 它可能会随着时间的推移而发展。
那么它就是一个完美的类候选对象。

1
在C++中,结构体和类之间唯一的区别是结构体默认为公共访问性。一个很好的指导原则是:将结构体用作仅保存数据的普通数据(POD),而在需要更多功能(成员函数)时使用类。
您可能仍在犹豫是在类中只使用公共变量还是使用成员函数,请考虑以下情况。
假设您有一个名为A的类,该类具有函数GetSomeVariable,它仅是一个私有变量的getter函数。
class A
{
    double _someVariable;

public:
    double GetSomeVariable() { return _someVariable; }
};

如果二十年后,那个变量的含义改变了,你需要将它乘以0.5怎么办?使用getter就很简单,只需返回变量乘以0.5即可。
    double GetSomeVariable() { return 0.5*_someVariable; }

通过这样做,您可以实现易于维护和修改。

避免使用下划线作为标识符的前缀是一个好主意。对于类成员变量,可以考虑使用 someVariable_m_someVariable - DavidRR

0

只为携带数据的被动对象使用结构体;其他一切都是类。

就像Google指南所说,我按照这种方式做,并发现它是一个好的规则。除此之外,我认为你可以定义自己的实用主义,或者如果确实有意义,可以偏离这个规则。


0

我不想在这里引发一场圣战;我通常会这样区分:

  • 对于POD对象(即仅包含数据,没有公开行为的对象),将内部声明为public并直接访问它们。在这里使用struct关键字很方便,也可以作为对象用途的提示。
  • 对于非POD对象,请将内部声明为private并定义公共的getter/setter。在这些情况下,使用class关键字更自然。

0

为了消除一些混淆和方便理解,这里有一些要点!

结构体中,你可以使用封装可见性运算符(使其成为私有公共)!就像你在类中所做的那样!

因此,有些人说或者你可能会在网上找到的说法:结构体的一个区别是没有可见性运算符和隐藏数据的能力,是错误的

你可以像在类中一样拥有方法!

运行下面的代码!你可以检查它是否编译良好!并且所有都正常运行!整个结构体的工作方式就像一样!

主要的区别只在于可见性模式的默认设置!

结构体是公共的!类默认为私有!

#include<iostream>
#include<string>

using namespace std;

int main(int argv, char * argc[]) {
    struct {
        private:
            bool _iamSuperPrivate = true;
            void _sayHallo() {
                cout << "Hallo mein Bruder!" << endl;
            }
        public:
            string helloAddress = "";
            void sayHellow() {
                cout << "Hellow!" << endl;
                
                if (this->helloAddress != "") {
                    cout << this->helloAddress << endl;
                }

                this->_sayHallo();
            }
            bool isSuperPrivateWorking() {
                return this->_iamSuperPrivate;
            }
    } testStruct;

    testStruct.helloAddress = "my Friend!";
    testStruct.sayHellow();

    if (testStruct.isSuperPrivateWorking()) {
        cout << "Super private is working all well!" << endl;
    } else {
        cout << "Super private not working LOL !!!" << endl;
    }

    return 0;
}

enter image description here

在内存中它们是相同的!

我没有亲自检查过!但有人说,如果你做同样的事情!编译后的汇编代码将在结构体之间相同!(待验证!)

取任何一个类,将名称更改为typedef struct!您会发现代码仍然可以正常工作!

class Client {

}

Client client(...);

=>

typedef struct Client {
 ....
} Client;

Client client(...);

如果你这样做,所有东西都会一样工作!至少我知道 gcc 中是这样的!

你可以在你的平台上测试!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接