假设您有以下代码:
int main(int argc, char** argv) {
Foo f;
while (true) {
f.doSomething();
}
}
首选Foo的以下两种实现中的哪一种?
解决方案1:
class Foo {
private:
void doIt(Bar& data);
public:
void doSomething() {
Bar _data;
doIt(_data);
}
};
解决方案2:
class Foo {
private:
Bar _data;
void doIt(Bar& data);
public:
void doSomething() {
doIt(_data);
}
};
用简单的英语:如果我有一个类经常被调用的方法,并且这个方法定义了大量的临时数据(复杂类的一个对象,或者大量的简单对象),我将此数据声明为该类的私有成员?
一方面,这将节省在每次通话中构建,初始化和破坏数据所花费的时间,从而提高性能。另一方面,它践踏了“私有成员=对象的状态”原则,并可能使代码更难理解。
答案是否取决于Bar类的大小/复杂程度?声明的对象数量如何?什么时候好处超过了缺点?
答案 0 :(得分:6)
首先,它取决于要解决的问题。如果需要在调用之间保留临时对象的值,则需要成员变量。如果需要在每次调用时重新初始化它们 - 请使用本地临时变量。这是一个手头的任务问题,而不是对或错。
临时变量构造和销毁将花费一些额外的时间(与仅保留成员变量相比),具体取决于临时变量类的复杂程度以及构造函数和析构函数必须执行的操作。决定成本是否显着只应在分析后进行,不要试图“以防万一”优化它。
答案 1 :(得分:6)
从设计的角度来看,如果数据不是对象状态的一部分,那么使用临时数会更加清晰,应该是首选。
在实际分析应用程序之前,切勿在性能方面做出设计选择。您可能会发现最终设计的设计实际上并不比原设计性能更好。
对于建议在构造/销毁成本较高时建议重用对象的所有答案,重要的是要注意,如果必须将对象从一个调用重用到另一个调用,则在很多情况下必须将对象重置为有效状态在方法调用之间,这也有成本。在许多此类情况下,重置的成本可与建筑/销毁相媲美。
如果你没有在调用之间重置对象状态,那么这两个解决方案可能会产生不同的结果,就像在第一次调用中一样,参数将被初始化,并且方法调用之间的状态可能会有所不同。
线程安全对此决定也有很大影响。函数内部的自动变量是在每个线程的堆栈中创建的,因此本质上是线程安全的。任何推动这些局部变量以便可以在不同调用之间重用的优化都会使线程安全性变得复杂,甚至可能因为争用可能会降低整体性能而导致性能下降。
最后,如果你想在方法调用之间保留对象,我仍然不会使它成为类的私有成员(它不是类的一部分)而是实现细节(静态函数变量,未命名的全局)实现doOperation的编译单元中的命名空间,PIMPL的成员... [前2个共享所有对象的数据,而后者仅适用于同一对象中的所有调用])类的用户不关心如何你解决问题(只要你安全地做,并记录该类不是线程安全的。)
// foo.h
class Foo {
public:
void doOperation();
private:
void doIt( Bar& data );
};
// foo.cpp
void Foo::doOperation()
{
static Bar reusable_data;
doIt( reusable_data );
}
// else foo.cpp
namespace {
Bar reusable_global_data;
}
void Foo::doOperation()
{
doIt( reusable_global_data );
}
// pimpl foo.h
class Foo {
public:
void doOperation();
private:
class impl_t;
boost::scoped_ptr<impl_t> impl;
};
// foo.cpp
class Foo::impl_t {
private:
Bar reusable;
public:
void doIt(); // uses this->reusable instead of argument
};
void Foo::doOperation() {
impl->doIt();
}
答案 2 :(得分:4)
在大多数情况下,我会将_data声明为临时变量。唯一的缺点是性能,但你会获得更多的好处。如果构造和破坏真的是性能杀手,你可能想尝试Prototype pattern。
答案 3 :(得分:1)
如果在Foo中保留Bar的值在语义上是正确的,那么将它作为成员是没有错的 - 那就是每个Foo 都有一个条。
有多种情况可能不正确,例如
大多数情况下,问题2是创建局部变量的原因:您希望确保从干净状态开始。
答案 4 :(得分:1)
像许多编码答案一样,它取决于。
解决方案1更加线程安全。因此,如果doSomething被许多线程调用,我将选择解决方案1。
如果您在单线程环境中工作并且创建Bar对象的成本很高,那么我将选择解决方案2.
在单线程环境中,如果创建Bar的成本很低,那么我认为我会选择解决方案1.
答案 5 :(得分:0)
你已经考虑过“私有成员=对象的状态”原则,所以重复这一点是没有意义的,但是,以另一种方式看待它。
一堆方法,比如a,b和c取数据“d”并一次又一次地处理它。该类没有其他方法可以关心这些数据。在这种情况下,你确定a,b和c是在正确的班级吗?
创建另一个较小的类和委托会更好吗,其中d可以是成员变量吗?这样的抽象很难想到,但往往会导致很好的代码。
只需2美分。
答案 6 :(得分:0)
这是一个极其简化的例子吗?如果没有,这样做有什么问题
void doSomething(Bar data);
int main() {
while (true) {
doSomething();
}
}
方式?如果doSomething()
是一个需要一些数据(Bar
)的纯算法,为什么需要将它包装在一个类中呢?一个类用于包装状态(数据)和方式(成员函数)来更改它。
如果您只需要一个数据,那就使用它:一段数据。如果您只需要一个算法,那么使用一个函数。只有当您需要在处理它们的多个算法(函数)的调用之间保持状态(数据值)时,类才可能是正确的选择。
我承认它们之间的界限很模糊,但IME是一个很好的经验法则。
答案 7 :(得分:0)
如果真的那么临时花费你的时间,那么我会说将它作为一个成员包含在你的班级中是没有错的。但请注意,如果在没有正确同步的情况下使用,这可能会使您的函数线程不安全 - 再次,这取决于_data
的使用。
但是,我会将这样的变量标记为mutable
。如果您读取成员为mutable
的类定义,则可以立即假设它不考虑其父对象的值。
class Foo {
private:
mutable Bar _data;
private:
void doIt(Bar& data);
public:
void doSomething() {
doIt(_data);
}
};
这也可以使用_data
作为const函数中的可变实体 - 就像你可以将它用作可变实体一样,如果它是这样一个函数中的局部变量。
答案 8 :(得分:-2)
如果您希望Bar仅初始化一次(在这种情况下由于成本)。然后我将它移动到单例模式。