我最近在代码审查中(由一位年龄更大,更聪明的C ++开发人员)告诉我重写了一个我写的类,而不是把它变成一组静态方法。他证明了这一点,虽然我的对象确实包含了非常少量的内部状态,但无论如何它都可以在运行时导出,如果我改为静态方法,我将避免在整个地方实例化对象的成本。
我现在已经做了这个改变,但它让我思考,C ++实例化的成本是多少?我知道在托管语言中,垃圾收集对象的所有成本都很重要。但是,我的C ++对象只是在堆栈上,它不包含任何虚拟方法,因此不存在运行时函数查找成本。我使用了新的C ++ 11删除机制来删除默认的复制/赋值运算符,因此不涉及复制。它只是一个带有构造函数的简单对象,它完成了少量的工作(无论如何都需要使用静态方法)和一个什么都不做的析构函数。无论如何可以告诉我这些不稳定会是什么? (审稿人有点吓人,我不想通过问他看起来很愚蠢!); - )
答案 0 :(得分:8)
简短回答 - 固有的对象分配很便宜,但在某些情况下可能会变得昂贵。
长答案
在C ++中,实例化对象的成本与在C中实例化结构相同。所有对象都是一块足以存储v表(如果有的话)和所有数据属性的内存块。在实例化v表后,方法不再使用内存。
非虚方法是一个简单函数,其隐式this
作为其第一个参数。调用虚函数有点复杂,因为它必须进行v表查找才能知道要调用哪个类的函数。
这意味着实例化堆栈上的对象涉及堆栈指针的简单递减(对于完整的降序堆栈)。
当在堆上实例化对象时,成本会大幅上升。但这是任何与堆相关的分配所固有的。在堆上分配内存时,堆需要找到一个足以容纳对象的空闲块。找到这样的块是非恒定时间操作并且可能很昂贵。
C ++具有可为某些指针数据属性分配更多内存的构造函数。这些通常是堆分配的。如果所述数据成员自己执行堆分配,则这进一步复杂化。这可能导致涉及大量指令的事情。
所以底线是它取决于你正在实施的对象的方式和内容。
答案 1 :(得分:6)
如果你的object-type必须在它的生命周期中调用一个非平凡的构造函数和析构函数,那么成本将是创建任何具有非平凡构造函数和析构函数的C ++对象的最低成本。制作其余方法static
不会降低费用。空间的“价格”至少为1个字节,因为您的类不是派生类的基类,static
类方法调用中唯一的成本节省将是省略隐式{ {1}}指针作为调用的隐藏的第一个参数传递,这是非静态类方法所必需的。
如果审核者要求您重新指定的方法为this
,则永远不要触及类类型的非静态数据成员,那么隐式static
指针的传递是资源浪费,审稿人有一个好点。否则,您必须向静态方法添加一个参数,该方法将类类型作为引用或指针,从而忽略了从隐式this
指针的省略中获得的性能。
答案 2 :(得分:4)
可能不是很多,如果它有任何瓶颈,我会感到惊讶。但是,如果没有其他原因,就有了这个原则。
然而,你应该问这个家伙;永远不要害怕这样做,而且在这里并不完全清楚,失去存储状态而是每次都得出它(如果这就是你正在做的事情)不会让事情变得更糟。而且,如果不是,你会认为命名空间比静态方法更好。
一个测试用例/例子可以让你更容易回答,而不是“你应该问他”。
答案 3 :(得分:3)
这取决于您的应用程序的功能。它是内存有限的设备上的实时系统吗?如果没有,大多数时候对象实例化都不会成为一个问题,除非你实例化数以百万计这些并保持它们或类似的奇怪设计。 大多数系统都会遇到更多瓶颈,例如:
我认为在大多数情况下,封装成一个设计类可以胜过实例化的小成本。当然可能有1%的情况不适用,但你的其中一个呢?
答案 4 :(得分:2)
作为一般规则,如果一个函数可以变为静态,它可能应该是。这更便宜。便宜多少钱?这取决于对象在其构造函数中的作用,但构造C ++对象的基本成本并不高(动态内存分配当然更昂贵)。
关键是不要为你不需要的东西买单。如果函数可以是静态的,为什么要使它成为成员函数?在这种情况下成为成员函数是没有意义的。创建对象的惩罚是否会破坏应用程序的性能?可能不会,但是,为什么要为你不需要的东西买单呢?
答案 5 :(得分:1)
正如其他人建议与你的同事谈谈并请他解释他的推理。如果可行的话,你应该用一个小测试程序调查这两个版本的性能。完成这两项工作将有助于您成长为程序员。
总的来说,我同意在可行的情况下使成员函数静态化的建议。不是因为性能原因,而是因为它减少了你需要记住的上下文量来理解函数的行为。
值得注意的是,有一种情况是使用成员函数会导致更快的代码。那种情况是编译器可以执行内联。这是一个高级主题,但是这样的东西很难编写关于编程的分类规则。
#include <algorithm>
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <time.h>
bool int_lt(int a, int b)
{
return a < b;
}
int
main()
{
size_t const N = 50000000;
std::vector<int> c1;
c1.reserve(N);
for (size_t i = 0; i < N; ++i) {
int r = rand();
c1.push_back(r);
}
std::vector<int> c2 = c1;
std::vector<int> c3 = c1;
clock_t t1 = clock();
std::sort(c2.begin(), c2.end(), std::less<int>());
clock_t t2 = clock();
std::sort(c3.begin(), c3.end(), int_lt);
clock_t t3 = clock();
std::cerr << (t2 - t1) / double(CLOCKS_PER_SEC) << '\n';
std::cerr << (t3 - t2) / double(CLOCKS_PER_SEC) << '\n';
return 0;
}
在我的i7 Linux上,因为g ++不能内联函数int_lt但可以内联std :: less :: operator(),非成员函数版本的速度要慢50%。
> g++-4.5 -O2 p3.cc
> ./a.out
3.85
5.88
要理解为什么这么大的差异需要考虑编译器推断出比较器的类型。在int_lt的情况下,它推断类型为bool(*)(int,int),而对于std :: less,它推断std :: less。使用函数指针,只能在运行时知道要调用的函数。这意味着编译器无法在编译时内联其定义。与std :: less相比,编译器在编译时可以访问类型及其定义,因此可以内联std :: less :: operator()。在这种情况下,这会对性能产生重大影响。
此行为是否仅与模板有关?不,它与将函数作为对象传递时丢失抽象有关。函数指针不包含与编译器要使用的函数对象类型一样多的信息。这是一个使用无模板的类似示例(为方便起见,除了std :: vector之外)。
#include <iostream>
#include <time.h>
#include <vector>
#include <stdlib.h>
typedef long (*fp_t)(long, long);
inline long add(long a, long b)
{
return a + b;
}
struct add_fn {
long operator()(long a, long b) const
{
return a + b;
}
};
long f(std::vector<long> const& x, fp_t const add, long init)
{
for (size_t i = 0, sz = x.size(); i < sz; ++i)
init = add(init, x[i]);
return init;
}
long g(std::vector<long> const& x, add_fn const add, long init)
{
for (size_t i = 0, sz = x.size(); i < sz; ++i)
init = add(init, x[i]);
return init;
}
int
main()
{
size_t const N = 5000000;
size_t const M = 100;
std::vector<long> c1;
c1.reserve(N);
for (size_t i = 0; i < N; ++i) {
long r = rand();
c1.push_back(r);
}
std::vector<long> c2 = c1;
std::vector<long> c3 = c1;
clock_t t1 = clock();
for (size_t i = 0; i < M; ++i)
long s2 = f(c2, add, 0);
clock_t t2 = clock();
for (size_t i = 0; i < M; ++i)
long s3 = g(c3, add_fn(), 0);
clock_t t3 = clock();
std::cerr << (t2 - t1) / double(CLOCKS_PER_SEC) << '\n';
std::cerr << (t3 - t2) / double(CLOCKS_PER_SEC) << '\n';
return 0;
}
粗略测试表明自由函数比成员函数慢100%。
> g++ -O2 p5.cc
> ./a.out
0.87
0.32
Bjarne Stroustrup最近提供了关于C ++ 11的精彩演讲,其中涉及到这一点。您可以在下面的链接中观看。
http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Keynote-Bjarne-Stroustrup-Cpp11-Style