这是一个相当古老的话题:主持人和吸尘者是好还是坏?
我的问题是:做编译器 C ++ / D / Java 内联吸气剂和二传手?
与直接字段访问相比,getter / setter在多大程度上影响性能(函数调用,堆栈帧)。除了使用它们的所有其他原因之外,我想知道它们是否应该影响性能,除了是一个好的OOP练习。
答案 0 :(得分:23)
这取决于。没有普遍的答案总是如此。
在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应该能够内联它。我不知道海湾合作委员会如何处理这个问题。
通过扩展,这也意味着如果getter是在不同的.dll / .so中定义的,则调用 可以不内联。
在任何情况下,我都不认为琐碎的获取/制定者必然是“良好的OOP实践”,或者“存在使用它们的所有其他原因”。很多人认为琐碎的获取/设置者1)是设计糟糕的标志,2)浪费打字。
就个人而言,这不是我对任何一种方式都有所了解的事情。对我而言,对于有资格作为“良好的OOP实践”的东西,它必须具有一些可量化的积极效果。琐碎的获取/制定者有一些边缘优势,有些也是微不足道的缺点。因此,我不认为他们是好的或坏的做法。如果你真的想要,它们就是你能做的事情。
答案 1 :(得分:9)
在D中,默认情况下,所有类的方法,但不是结构,都是虚拟的。您可以通过使方法或整个类最终来使方法非虚拟。此外,D具有属性语法,允许您创建公共字段,然后在不破坏源级兼容性的情况下将其更改为getter / setter。因此,在D中我建议只使用公共字段,除非你有充分的理由不这样做。如果你想出于某种原因使用琐碎的getter / setter,比如只有一个getter并且让这个变量只能从类外部读取,那就把它们作为最终的。
编辑:例如,行:
S s;
s.foo = s.bar + 1;
适用于两者
struct S
{
int foo;
int bar;
}
和
struct S
{
void foo(int) { ... }
int bar() { ... return something; }
}
答案 2 :(得分:6)
我有一次完全相同的问题。
回答问题,我编写了两个小程序:
第一个:
#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。
答案 3 :(得分:6)
我在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());
}
}
我知道,最后鲍勃比我年长一点,但为此,它应该没问题。
答案 4 :(得分:4)
任何有价值的JVM(或编译器)都需要支持内联。在C ++中,编译器内联getter和setter。在Java中,JVM在被称为“足够”时间后在运行时内联它们。我不知道D。
答案 5 :(得分:4)
根据您的类的预期演变,get / setter可能会使您的代码混乱,并且可以灵活地扩展您的实现而不会影响客户端代码。
我经常会遇到用作“数据容器”的类,它们对每个成员都有getter和setter。那是胡说八道。如果您不希望在获取或设置成员时需要触发某些“特殊”功能,请不要编写它。
鉴于机器的速度,编写额外抽象的努力将使您的客户花费更多的钱。
大多数编译器都可以优化琐碎的getter / setter,但只要你将它们声明为虚拟(在C ++中),你需要额外的查找 - 通常值得付出努力。
答案 6 :(得分:3)
在某些情况下,某些成员需要使用吸气剂。但是,为类的每个数据成员提供getter和setter并不是一个好习惯。这样做会使类的界面变得复杂。如果处理不当,setter也会带来资源所有权问题。
答案 7 :(得分:3)
引自here关于D Programming Langiage
我认为DMD目前无法对虚拟getter和setter进行去虚拟化。虚拟调用本身有点慢,但它们也不允许内联,因此无法进行连续的标准优化。因此,如果对属性的此类访问是虚拟调用,并且这发生在代码的“热”部分,则可能会显着降低代码速度。 (如果它发生在代码的非热点部分,它通常没有任何影响。这就是为什么Java Hot Spot不需要优化所有代码来生成一个非常快速的程序。)
I have encouraged Frits van Bommel改善了LDC的虚拟化能力:
现在LDC能够在很少的非常简单的情况下做到这一点,但大多数情况下与DMD相比情况没有变化。最终LLVM将会改进,因此这种情况本身可以得到改善。但前端也可能对此有所帮助。
以下是有关此主题的一些文档,some older,some more modern:
答案 8 :(得分:2)
在C ++中,当启用编译器的优化时,可以“内联”getter和setter:即,使用与直接访问底层成员数据相同的机器指令来实现。
答案 9 :(得分:1)
我会回应Xtofl。
吸气剂和制定者是有时候有用的东西之一,但是你会得到这些教条主义者,他们会以某种方式从“在某些情况下证明有用”到“必须一直使用,如果你不使用它”你是一个应该被杀的异教徒“。如果您对get或set有副作用,请务必使用getter或setter。
但如果没有,编写getter和setter只会使代码混乱。性能损失很小。在Java中,如果它只执行了几次,那么性能损失应该无关紧要,如果频繁执行,则应该内联,以便减轻惩罚。所以我更担心让代码更难阅读。
不要一分钟购买这个消除全球数据问题的论点。全球数据很糟糕,应该避免。但是将成员字段声明为私有然后创建公共getter和setter并不能解决问题。