这是一个相当古老的话题:setter 和 getter 是好还是坏?
我的问题是:C++/D/Java 中的编译器是否内联了 getter 和 setter?
与直接字段访问相比,getter/setter 在多大程度上影响性能(函数调用、堆栈帧)。除了使用它们的所有其他原因之外,我想知道它们是否应该影响性能,除了是一个好的 OOP 实践。
这取决于。没有永远正确的普遍答案。
在 Java 中,JIT 编译器迟早会内联它。据我所知,JVM JIT 编译器仅优化大量使用的代码,因此您最初可以看到函数调用开销,直到 getter/setter 被足够频繁地调用。
在 C++ 中,它几乎肯定会被内联(假设启用了优化)。但是,有一种情况可能不会:
// foo.h
class Foo {
private:
int bar_;
public:
int bar(); // getter
};
// foo.cpp
#include "foo.h"
int Foo::bar(){
return bar_;
}
如果类的用户看不到函数的定义(包括 foo.h,但看不到 foo.cpp),那么编译器可能无法内联函数调用。
如果启用链接时代码生成作为优化,MSVC 应该能够内联它。我不知道 GCC 如何处理这个问题。
通过扩展,这也意味着如果 getter 定义在不同的 .dll/.so 中,则不能内联调用。
无论如何,我认为微不足道的 get/setter 不一定是“良好的 OOP 实践”,或者“使用它们的所有其他原因”。很多人认为微不足道的 get/setter 1)是糟糕设计的标志,以及 2)浪费打字。
就个人而言,这不是我对任何一种方式都感到兴奋的事情。对我来说,要获得“良好的 OOP 实践”的资格,它必须具有一些可量化的积极影响。微不足道的 get/setter 有一些边际优势,还有一些同样微不足道的缺点。因此,我不认为它们是一个好的或坏的做法。如果您真的愿意,它们只是您可以做的事情。
在 D 中,类的所有方法,但不是结构,默认情况下都是虚拟的。您可以通过将方法或整个类设为 final 来使方法成为非虚拟方法。此外,D 具有属性语法,允许您将某些内容设为公共字段,然后稍后将其更改为 getter/setter,而不会破坏源代码级别的兼容性。因此,在 DI 中建议只使用公共字段,除非您有充分的理由不这样做。如果您出于某种原因想要使用琐碎的 getter/setter,例如只有一个 getter 并将变量从类外部设为只读,请将它们设为 final。
编辑:例如,行:
S s;
s.foo = s.bar + 1;
对双方都有效
struct S
{
int foo;
int bar;
}
和
struct S
{
void foo(int) { ... }
int bar() { ... return something; }
}
我在 Java 中尝试过:无论有没有 getter 和 setter,都是一样的。结果:两个版本的执行时间没有显着差异。这是代码:
class Person
{
public int age;
String name;
public Person(String name,int age)
{
this.name=name;
this.age=age;
}
}
class GetSetPerson
{
private int age;
String name;
public GetSetPerson(String name,int age)
{
this.name=name;
this.age=age;
}
public void setAge(int newage)
{
age=newage;
}
public int getAge()
{
return age;
}
}
class Proba
{
//Math.hypot kb 10-szer lassabb, mint a Math.sqrt(x*xy*y)!!!
public static void main(String args[])
{
long startTime, endTime, time;
int i;int agevar;
//Person p1=new Person("Bob",21);
GetSetPerson p1=new GetSetPerson("Bob",21);
startTime=System.nanoTime();
/*
for (i=0;i<1000000000;i++)
{
p1.age++;
}
*/
for (i=0;i<1000000000;i++)
{
agevar=p1.getAge();
agevar++;
p1.setAge(agevar);
}
endTime=System.nanoTime();
time=endTime-startTime;
System.out.println(""+time);
System.out.println(p1.name+"'s age now is "+p1.getAge());
}
}
我知道,最后鲍勃比我大一点,但为此,应该没问题。
我曾经有完全相同的问题。
为了回答它,我编写了两个小程序:
首先:
#include <iostream>
class Test
{
int a;
};
int main()
{
Test var;
var.a = 4;
std::cout << var.a << std::endl;
return 0;
}
第二:
#include <iostream>
class Test
{
int a;
int getA() { return a; }
void setA(int a_) { a=a_; }
};
int main()
{
Test var;
var.setA(4);
std::cout << var.getA() << std::endl;
return 0;
}
我使用 -O3 (完全优化)将它们编译为汇编程序并比较了这两个文件。他们是相同的。如果没有优化,它们是不同的。
这是在 g++ 下。所以,回答你的问题:编译器很容易优化 getter 和 setter。
任何值得一提的 JVM(或编译器)都需要支持内联。在 C++ 中,编译器内联 getter 和 setter。在 Java 中,JVM 在调用“足够”次数后在运行时内联它们。我不知道D。
根据您的类的预期演变,get/setter 可能会使您的代码混乱,而不是让您灵活地扩展您的实现而不影响客户端代码。
我经常遇到用作“数据容器”的类,每个成员都有 getter 和 setter。那是胡说八道。如果您不希望在获取或设置成员时需要触发某些“特殊”功能,请不要编写它。
鉴于机器的速度,编写额外抽象的工作将花费您的客户更多的钱。
大多数编译器可以优化琐碎的 getter/setter,但是一旦您将它们声明为虚拟的(在 C++ 中),您就需要支付额外的查找费用 - 通常值得付出努力。
在某些情况下,某些成员需要吸气剂。但是,为类的每个数据成员提供 getter 和 setter 并不是一个好习惯。这样做会使类的界面复杂化。如果处理不当,setter 也会带来资源所有权问题。
从这里引用关于 D 编程语言
我认为 DMD 目前无法对虚拟 getter 和 setter 进行去虚拟化。虚拟调用本身有点慢,但它们也不允许内联,因此无法进行连续的标准优化。因此,如果对属性的这种访问是虚拟调用,并且这发生在代码的“热”部分,那么它可能会显着减慢您的代码。(如果它发生在代码的非热点部分,它通常没有影响。这就是为什么 Java Hot Spot 不需要优化所有代码来生成一个非常快速的程序)。
我鼓励Frits van Bommel 提高 LDC 的去虚拟化能力:
现在 LDC 能够在一些非常简单的情况下做到这一点,但与 DMD 相比,大多数情况下情况并没有改变。最终 LLVM 会有所改善,因此这种情况可以自行改善。但前端也可能对此有所作为。
在 C++ 中,当启用编译器的优化时,getter 和 setter 可能会被“内联”:即,使用与直接访问底层成员数据相同的机器指令来实现。
我会回应 Xtofl。
getter 和 setter 是有时有用的东西之一,但你会得到这些教条主义的人,他们不知何故从“在某些情况下被证明有用”跳跃到“必须一直使用,如果你不使用它,你就是一个应该被杀的异端”。
如果您对 get 或 set 有副作用,请务必使用 getter 或 setter。
但如果不是这样,编写 getter 和 setter 只会使代码混乱。有一个小的性能损失。在 Java 中,如果它只执行几次,性能惩罚应该无关紧要,如果它经常执行,它应该被内联以减轻惩罚。所以我更担心使代码更难阅读。
并且不要相信这消除了全球数据问题的论点。全局数据不好,应该避免。但是将成员字段声明为私有然后创建公共 getter 和 setter 并不能解决问题。