(是的,我知道一台机器指令通常无关紧要。我问这个问题因为我想了解pimpl成语,并以最好的方式使用它;因为有时候我会这样做关心一台机器指令。)
在下面的示例代码中,有两个类Thing
和。{
OtherThing
。用户将包括“thing.hh”。
Thing
使用pimpl成语来隐藏它的实现。
OtherThing
使用C样式 - 返回和获取的非成员函数
指针。这种风格产生稍好的机器代码。我
想知道:有没有办法使用C ++风格 - 即制作功能
成员函数 - 但仍然保存机器指令。我喜欢这种风格,因为它不会污染类外的命名空间。
注意:我只关注调用成员函数(在本例中为calc
)。我不是在看对象分配。
以下是我的Mac上的文件,命令和机器代码。
thing.hh:
class ThingImpl;
class Thing
{
ThingImpl *impl;
public:
Thing();
int calc();
};
class OtherThing;
OtherThing *make_other();
int calc(OtherThing *);
thing.cc:
#include "thing.hh"
struct ThingImpl
{
int x;
};
Thing::Thing()
{
impl = new ThingImpl;
impl->x = 5;
}
int Thing::calc()
{
return impl->x + 1;
}
struct OtherThing
{
int x;
};
OtherThing *make_other()
{
OtherThing *t = new OtherThing;
t->x = 5;
}
int calc(OtherThing *t)
{
return t->x + 1;
}
main.cc(只是为了测试代码实际工作......)
#include "thing.hh"
#include <cstdio>
int main()
{
Thing *t = new Thing;
printf("calc: %d\n", t->calc());
OtherThing *t2 = make_other();
printf("calc: %d\n", calc(t2));
}
生成文件:
all: main
thing.o : thing.cc thing.hh
g++ -fomit-frame-pointer -O2 -c thing.cc
main.o : main.cc thing.hh
g++ -fomit-frame-pointer -O2 -c main.cc
main: main.o thing.o
g++ -O2 -o $@ $^
clean:
rm *.o
rm main
运行make
,然后查看机器代码。在Mac上我使用otool -tv thing.o | c++filt
。在linux上我认为它是objdump -d thing.o
。以下是相关输出:
事::计算值():
0000000000000000 movq(%rdi),%rax
0000000000000003 movl(%rax),%eax
0000000000000005含%eax
退货0000000000000007 计算值(OtherThing *):
0000000000000010 movl(%rdi),%eax
0000000000000012含%eax
0000000000000014 ret
由于指针间接,请注意额外的指令。第一个函数查找两个字段(impl,然后是x),而第二个函数只需要获取x。可以做些什么?
答案 0 :(得分:6)
一条指令很少花费很多时间来担心。首先,编译器可以将pImpl缓存在更复杂的用例中,从而在实际场景中分摊成本。其次,流水线架构使得几乎不可能预测时钟周期中的实际成本。如果你在一个循环中运行这些操作并计算时间差,你就可以更加真实地了解成本。
答案 1 :(得分:5)
不太难,只需在课堂上使用相同的技巧。任何中途不错的优化器都会内联 琐碎的包装。
class ThingImpl;
class Thing
{
ThingImpl *impl;
static int calc(ThingImpl*);
public:
Thing();
int calc() { calc(impl); }
};
答案 2 :(得分:2)
有一种令人讨厌的方法,就是用一个足够大的无符号字符数组替换指向ThingImpl
的指针然后放置/ new重新解释强制转换/显式破坏ThingImpl
对象。
或者你可以通过值传递Thing
,因为它应该不大于指向ThingImpl
的指针,尽管可能需要多一点{引用计数{{ 1}}会破坏优化,所以你需要一些标记'拥有'ThingImpl
的方法,这可能需要在某些架构上有额外的空间。)
答案 3 :(得分:2)
我不同意你的用法:你不是在比较两件事。
#include "thing.hh"
#include <cstdio>
int main()
{
Thing *t = new Thing; // 1
printf("calc: %d\n", t->calc());
OtherThing *t2 = make_other(); // 2
printf("calc: %d\n", calc(t2));
}
Thing
的构造函数完成。您应该在堆栈上分配Thing
,尽管它可能不会更改双重解除引用指令...但可能会更改其成本(删除缓存未命中)。
然而,重点是Thing
自己管理它的内存,所以你不能忘记删除实际内存,而你绝对可以使用C风格的方法。
我认为自动内存处理值得额外的内存指令,特别是因为据说,如果你多次访问它,可能会缓存解除引用的值,因此几乎没有。
正确性比表现更重要。
答案 4 :(得分:1)
让编译器担心它。它比我们更了解实际上更快或更慢的内容。特别是在如此微小的范围内。
在课程中拥有项目比仅仅封装有更多的好处。如果您忘记了如何使用私人关键字,那么PIMPL是一个好主意。