Getter& amp;的最佳性能二传手

时间:2014-09-09 14:54:43

标签: c++ performance getter-setter

最近我使用getter和setter方法进行了一些性能测试。我还不确定在c ++中使用它们的最佳方法是哪种。 (使用int等小类没有区别。)

string i;

string GetTest()
{
    return i;
}
void SetTest(string i)
{
    this->i = i; //copy
}

这将是方式,我将如何在java / C#中使用它。没有引用/指针,在c ++中非常慢。 对于字符串,向量和其他“大”类型,如果频繁调用它,这是非常慢和坏的

string i;
const string& GetTest()
{
    return i;
}
void SetTest(const string& i)
{
    this->i = i; //copy
}

目前我正在使用上面这个版本,它比不使用参数和返回值的引用要快得多,而且我确保它不会被更改。

string* i;

string& GetTest()
{
    return *i;
}
void SetTest(string& i)
{
    this->i = &i;
}

这个版本使用指针比使用引用更快,但我认为它更难阅读,如果你想使用类中的值,你必须使用指针。

除了使用引用/指针之外,它还可以通过内联频繁调用的getter& amp;来提高性能。因为它们是小功能,所以不可能。

inline GetTest(){....}
inline SetTest(){....}

但正如已经提到的,我仍然不确定在c ++中使用它们的最佳方法是什么,或者还有其他方法。消除这些含糊之处是很好的。

编辑:

近4年后再回到这里, 这是我现在给出的现代C ++的建议:(比任何文本都更有帮助)

https://www.youtube.com/watch?v=xnqTKD8uD64 51:00

我建议观看完整的视频,Herb Sutter这样一位出色的讲话者,一般都会关注CppCon。

5 个答案:

答案 0 :(得分:3)

[我在Simple C++ getter/setters完全从我之前的回答中复制了第一段。通常使用访问器/变换器是一种设计气味,你的类公共接口是不完整的。通常来说,您需要一个有用的公共接口,它提供有意义的功能而不是简单地获取/设置(这比我们在C中使用结构和函数更好一两步)。每次你想写一个mutator,并且很多时候你想写一个访问器,先退后一步,问问自己“我真的需要这个吗?”。

现在,如果你真的想拥有特定属性的getter / setter:前两个版本中的任何一个都是正确的,尽管第二个版本在某些情况下可能表现更好。 getter应该是constconst string& GetTest() const),因此可以在const对象上调用它们。

带指针的第三个版本非常危险。虽然前两个具有非常明确的所有权语义,但第三个没有。您显示的短片段没有清除,意味着设置函数的调用者必须在类的持续时间内保持字符串活动(没有所有权转移)。完全避免这种结构。

最后,如果您只是学习C ++,请考虑C ++图书清单中的一本或多本书The Definitive C++ Book Guide and List

编辑:如果您正在尝试提高代码的性能,最好的方法是编译优化,然后分析并查看热点的位置。

答案 1 :(得分:2)

如果您受限于C ++ 98 / C ++ 03,那么您的第二个代码确实是最佳解决方案。第三个代码容易出错并且是单一的,如果通过使用指针或更好的智能指针来修复它,作为参数,它仍然不理想(并且你还必须处理字符串的内存管理) )。

如果您可以使用C ++ 11或C ++ 14,那么间接保存数据并实现移动语义(如std::string)的类型的最佳setter实现是

void SetTest(std::string a_i) // by value!
{
  i = std::move(a_i); // move assignment
}

这是最优的原因是对于左值参数,它将花费与参考实现一样多(因为std::move阻止复制实际数据),但对于rvalue参数(例如返回)另一个函数的值,或者当显式使用std::move时)参数也是移动构造的,因此在这种情况下避免所有副本并且与第三个解决方案一样有效,没有它的问题。

请注意,对于大尺寸的类型(如具有许多成员的结构),已经很浅的副本将是昂贵的,因此即使在C ++ 11中,您的第二个版本仍然是这种类型的最佳版本。对于此类类型,除非该类另外管理分配的资源,否则附加的右值版本将提高性能。

当然,大多数情况下,正确的解决方案是根本没有吸气剂/固定剂。

答案 2 :(得分:1)

您在问题中列出的这3个版本做了3个完全不同的事情,这就是为什么它们以不同的速度运行。 "权利"在C ++中使用字符串getter和setter的方法是:

std::string _test;
const std::string& GetTest() const
{
    return _test; 
}
void SetTest(const std::string& test)
{
    this->_test = test; //copy
}
//optional, not necessary
void SetTest(std::string&& test)
{
    this->_test = std::move(test);
}

尽管其他一些贡献者说,吸气者和制定者不一定是糟糕的阶级设计。它们通常比直接访问成员变量更可取。但是,过度使用Getters / Setters(或直接访问成员变量)是一种代码味道。

您的第一个版本是可靠且正确的(除了Getter不是const之外,但它需要编译器在各种情况下不必要地复制和销毁字符串:

string GetTest()
{
    return i;
}
void SetTest(string i)
{
    this->i = i; //copy
}
...
// this is slower than the "right" way, because it requires creating a copy of the string and destroying it:
bool isEmpty = something.GetTest().size() != 0; // the "right" way does not create a copy here.
// this is slower than the "right way, because it requires creating a copy of the string and destroying it:
std::string myString("I am your father, Luke.");
something.SetTest(myString); // this copies "myString", then calls SetTest with the copy, then destroys the copy

您建议的第三个版本应该不惜一切代价避免,因为它是一个脆弱的设计,可以保证会导致意外的错误:

string* i;
string& GetTest()
{
    return *i;
}
void SetTest(string& i)
{
    this->i = &i;
}

// this modifies the string after the setter
std::string* myString = new std::string("I am your father, Luke.");
someting.SetTest(*myString);
myString->assign("Oranges");
// now this is true: someting.GetTest() == "Oranges"
delete myString;
// now this is undefined behaviour: someting.GetTest() == "Oranges"

"对"如果你真的真的需要java / c#般的行为,那么做第三个版本的方法如下:

std::shared_ptr<const std::string> _test;
const std::shared_ptr<const std::string>& GetTest() const
{
    return _test; 
}
void SetTest(const std::shared_ptr<const std::string>& test)
{
    this->_test = test;
}
void SetTest(std::shared_ptr<const std::string>&& test)
{
    this->_test = std::move(test);
}

答案 3 :(得分:0)

如果您在getter和setter中存储指向数据的指针,那么您将面临所有权和对象生命周期的重大问题。 Jave和C#有自己的处理方式。在C ++中,您可能需要使用各种智能指针(shared_ptr可能在这种情况下,但最好的指导是测试您获得的实际性能。)

答案 4 :(得分:0)

对于大多数C ++编译器,无论你对它们做什么,都会内联小的get / set方法。如果您在头文件中定义实现,那么它们肯定会被内联!

唯一的例外是它们是否构建在一个共享库中(内联到调用者代码中很棘手)但是即使这样,你也可以使用现代编译器中的链接时内联进行编译然后进行内联那里也是!

即使是复杂的类型,也有一种称为&#34; return value optimisation&#34; (或RVO)您的返回类型被编译为使用与调用者使用的类型相同的存储 - 即整个副本被优化掉。

诀窍是允许编译器通过保持代码简单来完成它的工作。它可以测试你的代码的性能,但我认为你无论如何都会调整其他领域的性能。

PS。吸气鬼和二传手......他们是a bad design。不要鼓励他们。