2010-02-28 44 views
4

我正在从Java转向C++,并且在理解C++类的基本工作原理以及设计它们的最佳实践方面遇到了很多麻烦。具体来说,我想知道是否应该在以下情况下使用指向我的类成员的指针。C++:如何提高将经常复制的自定义类的性能?

我有一个自定义类Foo,它表示某个特定回合的游戏状态,而Foo有一个自定义类Bar的成员变量,它表示该游戏状态的逻辑子集。例如,Foo代表棋盘,Bar代表受到攻击的棋子和逃脱棋子(不是我的具体情况,而是我认为更普遍的比喻)。

我想通过复制Foo并相应地更新副本的状态来搜索一系列移动。当我完成搜索移动序列时,我将放弃该副本,并仍然有我的原始Foo表示当前的游戏状态。

foo.h中,我宣布我的Foo类,我声明一个成员变量为它类型栏:在我的Foo构造我打电话酒吧构造与一些特定参数的实施

class Foo { 
    Bar b; 
public: 
    Foo(); 
    Foo(const Foo& f); 
} 

但到我将在运行时知道的当前状态。据我所知,这意味着一个Bar构造函数被调用两次 - 一次是因为我写了“Bar b;”如果我理解正确,它会调用默认构造函数),并且曾经因为我在Foo :: Foo()和Foo :: Foo(const Foo())中写入类似“b = Bar(arg1,arg2,...) f)。

如果我想尽可能多地制作Foo的拷贝,这是一个问题,对吧?

我在想一个简单的解决方案是声明一个指向Bar的指针,而不是:“Bar * b”,它应该避免两次实例化b。这是好的做法吗?这是否表明我应该知道的任何陷阱?有更好的解决方案吗?我找不到一个具体的例子来帮助我(除了很多关于使用指针的警告),并且关于设计类的所有信息都非常令人难以置信,所以任何指导都将不胜感激!

编辑:是的,当我创建我的Foo时,我将获得创建Bar所需的所有信息。我认为每个人都推断出这一点,但要清楚,我有更多的东西像这样已经为我的默认构造函数:

Foo(int k=5); 

和Foo.cpp中:

Foo::Foo(int k) { 
    b = Bar(k); 
    ... 
} 

,然后我Foo和其酒吧成员随着游戏状态的变化而逐步更新。

所以叫我的自定义栏的构造在我的Foo构造 声明 初始化列表看起来做的最好的方式。谢谢你的答案!

+1

当声明refernces,这是一个很好的做法写'常量富&f',因为这很容易暗示f是参考。请记住,'const Foo & x, y;'意思是'x'是一个参考,但'y'不是。 – gruszczy 2010-02-28 18:58:04

+0

@gruszczy:你的写作就像是普遍遵循的规则。但事实并非如此。这是个人偏好。两种方式都有很好的理由。 – sbi 2010-02-28 19:13:00

+0

是的,缩进代码也不是普遍遵循的规则,这是一些人的个人偏好。但我不相信任何这些做法都有很好的理由。 – gruszczy 2010-02-28 19:47:38

回答

2

理想情况下,您可以在构建Foo时设置Bar所需的所有信息。最好的解决办法是这样的:(这在Java中没有直接等效)

class Foo { 
    Bar b; 
public: 
    Foo() : b() { ... }; 
    Foo(const Foo& f) : b(f.a, f.b) { ... }; 
} 

了解更多关于constructor initialization lists

使用指针pb = new Bar(arg1, arg2)实际上将最有可能恶化因为堆分配你的表现(其中可能涉及锁定等)可能容易变得比将非默认构造的Bar分配给默认构造的Bar(当然取决于您的Bar::operator=的复杂程度如何)更加昂贵。)

+0

感谢关于堆分配的珍闻。我从目前的每个答案中学到了一些新东西,但现在我会接受这个答案:) – Imran 2010-02-28 19:26:51

+0

地球上的新操作怎么会导致锁定? C++标准对并发问题没有任何假设。 – 2010-02-28 19:33:40

+1

@哈桑,你的意思是?你是否声称只是因为标准“对某些东西没有推测”而认为在标准实现中某些东西是不存在的? *如果* *不链接到多线程运行时,或者如果使用'tcmalloc',但是您还考虑了数据局部性和缓存性能,您可能会*处理锁定处罚?我猜不会!在所有情况下,堆管理都是不平凡的(不同程度的)操作,在对性能进行编码时最好避免。 – vladr 2010-02-28 19:43:44

0

是的,你可以使用一个指针成员并像这样实例化:b = new Bar(arg1, arg2, ...)。根据你的描述,我会这样做。只记得删除它(可能在Foo析构函数中):delete b;或者你会泄漏它。

如果您是在Foo的构造函数中创建它,并且您知道参数,则可以保持您的成员不变,并在初始化程序列表中明确调用构造函数,并仅执行一次:Foo() : b(arg1, arg2, ...) {}。那么你不必调用delete。

+1

-1用于提示动态内存管理,+1用于提示初始化列表。这使它为零:) – Tronic 2010-02-28 18:54:36

+0

只是试图说明OP的差异。 – 2010-02-28 20:21:12

+0

是的,感谢,谢谢! – Imran 2010-02-28 22:03:00

2

编译器非常擅长优化不必要的复制(即使自定义复制文件已定义,该标准也可以避免复制构造函数的调用)。在头文件中实现短的函数也允许更多的优化,因为编译器可以看到它的内部并且可能避免一些不必要的处理。

在成员变量b的情况下,也许你可以使用初始化列表以避免出现两个初始化:

Foo(): b(arg1, arg2) {} 

在一般情况下,随之而来的优化,首先使其工作,才标杆,看看它需要做得更快,如果是这样,轮廓找出瓶颈在哪里。

+0

“总的来说,优化的步骤是先让它工作,然后才能进行基准测试,看看它是否需要加快速度,如果是的话,找出瓶颈的位置。” - 感谢您的提醒!过去我很难学会这一点,但我仍然喜欢在设计时“优化”,如果它意味着使用一种通用的模式,而不是以一种天真的方式做一些事情,然后意识到它很慢并且没有人这样做无论如何! :) – Imran 2010-02-28 19:09:20

0

如果您确实使用new bar,则使用auto_ptr - 只要bar仅用于foo(否则为boost :: shared_ptr,但这些可能会很慢)。

现在关于默认构造函数...你可以通过提供摆脱的是:

  1. 一个initializer list为Foo构造 - 这将初始化吧。

  2. 其次是用于初始化程序列表中的bar的非默认构造函数。

但是,在这一点上,我觉得你正在从一个痣山创造一座山。用Java以这种方式思考可能会为您节省大量的CPU周期。然而,在C++中,默认构造函数不(通常)意味着很多开销。

+1

他正在寻找性能。堆分配可能会杀死他的表现。 – vladr 2010-02-28 19:07:29

+0

@vlad这就是为什么我提到初始化列表和非默认构造函数。 – 2010-02-28 19:14:25

+0

@Hassan:尽管如此,动态记忆将成为你想法的瓶颈。我和弗拉德在一起。 – sbi 2010-02-28 19:17:48

1

在我的Foo构造函数的实现中我使用一些特定于当前状态的参数调用Bar构造函数,我将在运行时了解这些参数。据我所知,这意味着一个Bar构造函数被调用两次 - 一次是因为我写了“Bar b;”如果我理解正确,它会调用默认构造函数),并且曾经因为我在Foo :: Foo()和Foo :: Foo(const Foo())中写入类似“b = Bar(arg1,arg2,...) f)。

你弄错了代码。 C++可以做的更好:

当你在类Foo成员Bar bar;那么这意味着Foo每个实例确实将有一个名为barBar一个实例。只要创建了Foo对象,即创建Foo对象时,就会创建此bar对象。

您可以控制的Foobar子对象的创建中初始化列表Foo的构造

Foo::Foo(Arg1 arg1, Arg2 arg2) 
    : bar(arg1,arg2) 
{ 
} 

该行开始用冒号将告诉编译器,其中Bar构造在使用Foo的这个特定构造函数创建Foo对象时调用它。在我的示例中,Bar的构造函数需要调用Arg1Arg2

如果不指定Bar构造函数用于创建一个Foo对象的bar子对象,那么编译器将使用Bar的默认构造函数。 (这是不带任何参数的一个。)

如果调用的Foo编译器生成的构造函数(编译器生成默认的,在某些情况下,拷贝构造函数为你),那么编译器会选择相应的Bar构造:Foo的默认构造函数将调用Bar的默认构造函数,Foo的复制构造函数将调用Bar的复制构造函数。如果你想覆盖这些默认值,你必须自己明确地编写这些Foo构造函数(而不是依赖编译器生成的构造函数),给它们一个初始化列表,其中调用正确的Bar构造函数。

所以你必须支付每个Foo建设的Bar施工。除非多个Foo对象可以共享相同的对象(使用写时复制,轻量级或类似的方式),那么您必须付费。

(在一个侧面说明:。这同样适用于分配IME,然而,这是非常罕见的是分配应该从默认的行为偏离)

+0

Errr我不认为他得到了第一部分错误。他正确地认为,在他的原始代码中,关于被调用的'Bar()'和'Bar(arg1,arg2)'(以及“operator =')。 – vladr 2010-02-28 19:05:39

+0

@Vlad:我不确定你在做什么。我说他弄错了代码。也许我以某种方式说过,以至于不清楚?我会尽量更加明确。 – sbi 2010-02-28 19:10:44

+0

我刚刚写了Bar(){cout <<“默认栏构造函数名为”},并在我的Foo构造函数中调用的自定义Bar构造函数中放置了一个类似的cout,并看到当我运行“Foo f;”时调用了这两个函数,对我的问题。如果我按照描述使用初始化列表,这种效果会消失吗? – Imran 2010-02-28 19:28:42